Skip to content

Commit c5f4152

Browse files
committed
api: fix large output crash, truncate tool results >2k chars with sha256
1 parent 62d6f17 commit c5f4152

File tree

3 files changed

+53
-9
lines changed

3 files changed

+53
-9
lines changed

api_server.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,9 @@ def _build_env():
144144
}
145145

146146

147+
_STREAM_LIMIT = 100 * 1024 * 1024 # 100MB — claude lines can be huge
148+
149+
147150
async def _stream(workspace: str, req: RunRequest):
148151
env = _build_env()
149152

@@ -155,6 +158,7 @@ async def _stream(workspace: str, req: RunRequest):
155158
stderr=asyncio.subprocess.STDOUT,
156159
cwd=workspace,
157160
env=env,
161+
limit=_STREAM_LIMIT,
158162
)
159163
busy_workspaces[workspace] = proc
160164
if proc.stdout:
@@ -170,6 +174,7 @@ async def _stream(workspace: str, req: RunRequest):
170174
stderr=asyncio.subprocess.STDOUT,
171175
cwd=workspace,
172176
env=env,
177+
limit=_STREAM_LIMIT,
173178
)
174179
busy_workspaces[workspace] = proc
175180

@@ -187,6 +192,7 @@ async def _stream(workspace: str, req: RunRequest):
187192
stderr=asyncio.subprocess.STDOUT,
188193
cwd=workspace,
189194
env=env,
195+
limit=_STREAM_LIMIT,
190196
)
191197
busy_workspaces[workspace] = proc
192198
if proc.stdout:

jsonverbose.py

Lines changed: 25 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,12 @@
66
that combines the final result with the full conversation history.
77
"""
88

9+
import hashlib
910
import json
1011
import sys
1112

13+
_CONTENT_TRUNCATE = 2000 # chars to keep before truncating
14+
1215

1316
def _extract_tool_uses(content):
1417
"""Extract tool_use entries from assistant message content."""
@@ -36,26 +39,39 @@ def _extract_text(content):
3639
return out
3740

3841

42+
def _truncate_content(text: str) -> dict:
43+
"""Return content dict, truncating if over limit with sha256 + length."""
44+
if len(text) <= _CONTENT_TRUNCATE:
45+
return {"content": text}
46+
sha = hashlib.sha256(text.encode()).hexdigest()
47+
return {
48+
"content": text[:_CONTENT_TRUNCATE],
49+
"truncated": True,
50+
"total_length": len(text),
51+
"sha256": sha,
52+
}
53+
54+
3955
def _extract_tool_results(content):
4056
"""Extract tool_result entries from user message content."""
4157
out = []
4258
for block in content:
4359
if block.get("type") != "tool_result":
4460
continue
45-
entry = {
46-
"type": "tool_result",
47-
"tool_use_id": block.get("tool_use_id", ""),
48-
"is_error": block.get("is_error", False),
49-
}
5061
raw = block.get("content", "")
5162
if isinstance(raw, str):
52-
entry["content"] = raw
63+
text = raw
5364
elif isinstance(raw, list):
54-
# content can be a list of blocks
5565
texts = [b.get("text", "") for b in raw if b.get("type") == "text"]
56-
entry["content"] = "\n".join(texts) if texts else str(raw)
66+
text = "\n".join(texts) if texts else str(raw)
5767
else:
58-
entry["content"] = str(raw)
68+
text = str(raw)
69+
entry = {
70+
"type": "tool_result",
71+
"tool_use_id": block.get("tool_use_id", ""),
72+
"is_error": block.get("is_error", False),
73+
**_truncate_content(text),
74+
}
5975
out.append(entry)
6076
return out
6177

tests/test_api.sh

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -260,6 +260,27 @@ test_api_json_verbose() {
260260
_api_stop "${API_CONTAINER}-jv"
261261
}
262262

263+
# ── large output (>64KB line) doesn't crash ─────────────────────────────────
264+
265+
test_api_large_output() {
266+
_api_start "${API_CONTAINER}-large" || return 1
267+
268+
# generate a file >64KB and ask claude to read it — tool result will be one big JSON line
269+
local big_file="/tmp/bigfile_$$.txt"
270+
python3 -c "print('X' * 70000)" > "$big_file"
271+
curl -sf -X PUT "$API_BASE/files/bigtest.txt" --data-binary @"$big_file" >/dev/null
272+
rm -f "$big_file"
273+
274+
local out code
275+
code=$(curl -s -o /dev/null -w "%{http_code}" -X POST "$API_BASE/run" \
276+
-H "Content-Type: application/json" \
277+
-d "{\"prompt\": \"read the file /workspaces/bigtest.txt and tell me how many characters it has\", \"model\": \"$TEST_MODEL\", \"noContinue\": true}")
278+
assert_eq "$code" "200" "large output does not crash (no 500)" || { _api_stop "${API_CONTAINER}-large"; return 1; }
279+
280+
echo "OK: api_large_output"
281+
_api_stop "${API_CONTAINER}-large"
282+
}
283+
263284
ALL_TESTS+=(
264285
test_api_endpoints
265286
test_api_run
@@ -272,4 +293,5 @@ ALL_TESTS+=(
272293
test_api_append_system_prompt
273294
test_api_continue_fallback
274295
test_api_json_verbose
296+
test_api_large_output
275297
)

0 commit comments

Comments
 (0)