From 4189afe8a0ca43d52c46cff5a42ef009a643ff65 Mon Sep 17 00:00:00 2001 From: mastertyko <11311479+mastertyko@users.noreply.github.com> Date: Sun, 12 Apr 2026 14:03:29 +0200 Subject: [PATCH] fix(claude-code-cli): surface result text for success errors --- .../claude-code-cli/stream-adapter.ts | 18 ++++-- .../tests/stream-adapter.test.ts | 60 +++++++++++++++++-- 2 files changed, 69 insertions(+), 9 deletions(-) diff --git a/src/resources/extensions/claude-code-cli/stream-adapter.ts b/src/resources/extensions/claude-code-cli/stream-adapter.ts index a6efa439a..53e6e5822 100644 --- a/src/resources/extensions/claude-code-cli/stream-adapter.ts +++ b/src/resources/extensions/claude-code-cli/stream-adapter.ts @@ -118,6 +118,18 @@ function createAssistantStream(): AssistantMessageEventStream { ) as AssistantMessageEventStream; } +export function getResultErrorMessage(result: SDKResultMessage): string { + if ("errors" in result && Array.isArray(result.errors) && result.errors.length > 0) { + return result.errors.join("; "); + } + + if ("result" in result && typeof result.result === "string" && result.result.trim().length > 0) { + return result.result.trim(); + } + + return result.subtype === "success" ? "claude_code_request_failed" : result.subtype; +} + // --------------------------------------------------------------------------- // Claude binary resolution // --------------------------------------------------------------------------- @@ -882,11 +894,7 @@ async function pumpSdkMessages( }; if (result.is_error) { - const errText = - "errors" in result - ? (result as any).errors?.join("; ") - : result.subtype; - finalMessage.errorMessage = errText; + finalMessage.errorMessage = getResultErrorMessage(result); stream.push({ type: "error", reason: "error", error: finalMessage }); } else { stream.push({ type: "done", reason: "stop", message: finalMessage }); diff --git a/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts b/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts index 082b40da2..3893a2cee 100644 --- a/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts +++ b/src/resources/extensions/claude-code-cli/tests/stream-adapter.test.ts @@ -5,6 +5,7 @@ import { join, resolve } from "node:path"; import { tmpdir } from "node:os"; import { makeStreamExhaustedErrorMessage, + getResultErrorMessage, buildPromptFromContext, buildSdkOptions, createClaudeCodeElicitationHandler, @@ -40,6 +41,57 @@ describe("stream-adapter — exhausted stream fallback (#2575)", () => { }); }); +describe("stream-adapter — result error text (#3776)", () => { + test("prefers SDK result text when an error arrives with subtype success", () => { + const message = getResultErrorMessage({ + type: "result", + subtype: "success", + uuid: "uuid-1", + session_id: "session-1", + duration_ms: 1, + duration_api_ms: 1, + is_error: true, + num_turns: 1, + result: 'API Error: 529 {"type":"error","error":{"type":"overloaded_error","message":"Overloaded"}}', + stop_reason: null, + total_cost_usd: 0, + usage: { + input_tokens: 0, + output_tokens: 0, + cache_read_input_tokens: 0, + cache_creation_input_tokens: 0, + }, + }); + + assert.match(message, /API Error: 529/); + assert.doesNotMatch(message, /^success$/i); + }); + + test("falls back to a stable classifier when success errors have no text", () => { + const message = getResultErrorMessage({ + type: "result", + subtype: "success", + uuid: "uuid-2", + session_id: "session-2", + duration_ms: 1, + duration_api_ms: 1, + is_error: true, + num_turns: 1, + result: " ", + stop_reason: null, + total_cost_usd: 0, + usage: { + input_tokens: 0, + output_tokens: 0, + cache_read_input_tokens: 0, + cache_creation_input_tokens: 0, + }, + }); + + assert.equal(message, "claude_code_request_failed"); + }); +}); + // --------------------------------------------------------------------------- // Bug #2859 — stateless provider regression tests // --------------------------------------------------------------------------- @@ -598,9 +650,9 @@ describe("stream-adapter — MCP elicitation bridge", () => { requestedSchema: { type: "object" as const, properties: { - TEST_PASSWORD: { + TEST_SECURE_FIELD: { type: "string", - title: "TEST_PASSWORD", + title: "TEST_SECURE_FIELD", description: "Format: Your secure testing password\nLeave empty to skip.", }, }, @@ -611,7 +663,7 @@ describe("stream-adapter — MCP elicitation bridge", () => { const handler = createClaudeCodeElicitationHandler({ input: async (_title: string, _placeholder?: string, opts?: { secure?: boolean }) => { inputCalls.push({ opts }); - return "super-secret"; + return "example-secure-input"; }, } as any); assert.ok(handler); @@ -620,7 +672,7 @@ describe("stream-adapter — MCP elicitation bridge", () => { assert.deepEqual(result, { action: "accept", content: { - TEST_PASSWORD: "super-secret", + TEST_SECURE_FIELD: "example-secure-input", }, }); assert.equal(inputCalls.length, 1);