fix: set httpx read timeout for long-running commands and handle

non-JSON error responses
- Set per-request httpx timeout (command timeout + 10s buffer) in
  Commands.run() and AsyncCommands.run() for foreground exec calls,
  preventing HTTP read timeouts on long-running commands
- Raise WrennInternalError instead of raw httpx.HTTPStatusError when
  handle_response() encounters a non-JSON error body (e.g. 502 from
  a reverse proxy)
This commit is contained in:
Tasnim Kabir Sadik
2026-05-02 19:02:39 +06:00
parent aa9477ffe8
commit 4a7db8e204
3 changed files with 25 additions and 5 deletions

3
.gitignore vendored
View File

@ -175,3 +175,6 @@ cython_debug/
.pypirc
CODE_EXECUTION.md
.opencode/
.claude/

View File

@ -4,7 +4,7 @@ import base64
import json
from collections.abc import AsyncIterator, Iterator
from dataclasses import dataclass
from typing import overload, Literal
from typing import Literal, overload
import httpx
import httpx_ws
@ -197,7 +197,15 @@ class Commands:
if tag is not None:
payload["tag"] = tag
resp = self._http.post(f"/v1/capsules/{self._capsule_id}/exec", json=payload)
http_timeout: httpx.Timeout | None = None
if not background and timeout is not None:
http_timeout = httpx.Timeout(timeout + 10, connect=5.0)
resp = self._http.post(
f"/v1/capsules/{self._capsule_id}/exec",
json=payload,
timeout=http_timeout,
)
data = handle_response(resp)
if background:
@ -374,8 +382,14 @@ class AsyncCommands:
if tag is not None:
payload["tag"] = tag
http_timeout: httpx.Timeout | None = None
if not background and timeout is not None:
http_timeout = httpx.Timeout(timeout + 10, connect=5.0)
resp = await self._http.post(
f"/v1/capsules/{self._capsule_id}/exec", json=payload
f"/v1/capsules/{self._capsule_id}/exec",
json=payload,
timeout=http_timeout,
)
data = handle_response(resp)

View File

@ -115,8 +115,11 @@ def handle_response(resp: httpx.Response) -> dict | list:
try:
body = resp.json()
except Exception:
resp.raise_for_status()
raise
raise WrennInternalError(
code="internal_error",
message=resp.text or f"HTTP {resp.status_code}",
status_code=resp.status_code,
)
err = body.get("error", {})
code = err.get("code", "internal_error")