fix: detect mid-stream truncation and emit claude_truncated error code (FAR-95)

When Claude produces assistant content (output_tokens > 0) but the stream ends
without a result event, classify the run as truncated mid-stream rather than
falling through to the generic "did not produce a result — check API
credentials" message. The misleading hint pointed operators at auth/model
config when the real cause was pod termination, OOMKill, or CLI crash.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
This commit is contained in:
2026-04-26 01:54:35 +00:00
committed by Hugh Commit [agent]
parent 818aa0f1d6
commit a2874c0426
4 changed files with 104 additions and 0 deletions
+44
View File
@@ -224,6 +224,50 @@ more raw output`;
expect(result.resultJson).not.toBeNull();
});
it("sets truncatedMidStream=true when assistant event with output_tokens>0 has no result (FAR-95)", () => {
const initLine = JSON.stringify({ type: "system", subtype: "init", model: "claude-opus-4-7", session_id: "sess_1" });
const assistantEvent = JSON.stringify({
type: "assistant",
session_id: "sess_1",
message: {
id: "msg_abc",
stop_reason: null,
usage: { input_tokens: 1, output_tokens: 35, cache_creation_input_tokens: 523, cache_read_input_tokens: 46295 },
content: [{ type: "tool_use", id: "tool_1", name: "Bash", input: { command: "echo hi" } }],
},
});
const result = parseClaudeStreamJson([initLine, assistantEvent].join("\n"));
expect(result.truncatedMidStream).toBe(true);
expect(result.llmApiEmptyResponse).toBe(false);
expect(result.resultJson).toBeNull();
});
it("clears truncatedMidStream when a result event follows assistant content", () => {
const assistantEvent = JSON.stringify({
type: "assistant",
message: { stop_reason: null, usage: { output_tokens: 35 }, content: [] },
});
const resultEvent = JSON.stringify({
type: "result",
result: "Done",
subtype: "stop",
total_cost_usd: 0.001,
usage: { input_tokens: 10, output_tokens: 5, cache_read_input_tokens: 0 },
});
const result = parseClaudeStreamJson([assistantEvent, resultEvent].join("\n"));
expect(result.truncatedMidStream).toBe(false);
expect(result.resultJson).not.toBeNull();
});
it("does not set truncatedMidStream when assistant has output_tokens=0", () => {
const assistantEvent = JSON.stringify({
type: "assistant",
message: { stop_reason: null, usage: { output_tokens: 0 }, content: [] },
});
const result = parseClaudeStreamJson(assistantEvent);
expect(result.truncatedMidStream).toBe(false);
});
it("sets llmApiEmptyResponse=false for normal result", () => {
const resultEvent = JSON.stringify({
type: "result",