From dc23c976bfb3884566c172bbcfad21ca8cee4546 Mon Sep 17 00:00:00 2001 From: "altimate-factory[bot]" Date: Thu, 29 Oct 2026 10:15:05 +0000 Subject: [PATCH] fix: automated fix in packages/opencode/src/session Factory lane: altimate_code. Agent-Authored-By: altimate-factory --- packages/opencode/src/session/processor.ts | 8 +++ packages/opencode/src/session/prompt.ts | 2 + .../processor-tool-abort-telemetry.test.ts | 52 +++++++++++++++++++ 3 files changed, 62 insertions(+) create mode 100644 packages/opencode/test/session/processor-tool-abort-telemetry.test.ts diff --git a/packages/opencode/src/session/processor.ts b/packages/opencode/src/session/processor.ts index bcbbabca6a..a75f59c2bb 100644 --- a/packages/opencode/src/session/processor.ts +++ b/packages/opencode/src/session/processor.ts @@ -153,6 +153,14 @@ export namespace SessionProcessor { export function shouldTrackStreamingError(error: unknown, sessionAborted: boolean): boolean { return !(sessionAborted && MessageV2.AbortedError.isInstance(error)) } + + // The same rule for tool parts: an unfinished tool closed by interruptedToolPart + // after the session's abort signal fired is the requested cancellation, not a + // tool failure. Interruptions the session did not request are still reported. + /** @internal Exported for focused telemetry tests. */ + export function shouldTrackToolError(part: MessageV2.ToolPart, sessionAborted: boolean): boolean { + return !(sessionAborted && part.state.status === "error" && part.state.metadata?.interrupted === true) + } // altimate_change end // A provider may repeat one malformed raw tool-call id for concurrent calls. diff --git a/packages/opencode/src/session/prompt.ts b/packages/opencode/src/session/prompt.ts index f6e2c0c0f8..0513496672 100644 --- a/packages/opencode/src/session/prompt.ts +++ b/packages/opencode/src/session/prompt.ts @@ -2195,6 +2195,8 @@ export namespace SessionPrompt { // altimate_change end if (toolChain.length < 50) toolChain.push(part.tool) const isError = part.state?.status === "error" + // A tool the user's own abort interrupted did not fail; it is neither an error nor a recovery. + if (isError && !SessionProcessor.shouldTrackToolError(part, abort.aborted)) continue if (isError) { toolErrorCount++ // Flush previous unrecovered error before recording new one diff --git a/packages/opencode/test/session/processor-tool-abort-telemetry.test.ts b/packages/opencode/test/session/processor-tool-abort-telemetry.test.ts new file mode 100644 index 0000000000..1e79f41be4 --- /dev/null +++ b/packages/opencode/test/session/processor-tool-abort-telemetry.test.ts @@ -0,0 +1,52 @@ +// A user's Stop/Esc closes every unfinished tool part as "Tool execution aborted" +// with metadata.interrupted. Those parts were reported as `error_fingerprint` +// tool errors ("tool aborted" on warehouse_test). Only interruptions the +// session requested are excluded; every other tool error is still reported. +import { describe, expect, test } from "bun:test" +import type { MessageV2 } from "../../src/session/message-v2" +import { SessionProcessor } from "../../src/session/processor" +import { ErrorDetail } from "../../src/altimate/telemetry/error-detail" +import { Telemetry } from "../../src/altimate/telemetry" + +// interruptedToolPart (src/session/processor.ts) is the only producer of this tool error. +const INTERRUPTED_ERROR = "Tool execution aborted" + +describe("reported warehouse_test fingerprint", () => { + test("is exactly the fingerprint of an interrupted tool part", () => { + expect(ErrorDetail.describeToolError(INTERRUPTED_ERROR)).toEqual({ + errorHash: "18cc068fbdbecb19", + errorMessage: "tool aborted", + }) + expect(Telemetry.classifyError(INTERRUPTED_ERROR)).toBe("unknown") + }) +}) + +function toolPart(state: Record): MessageV2.ToolPart { + return { + id: "prt_1", + sessionID: "ses_1", + messageID: "msg_1", + type: "tool", + callID: "call_1", + tool: "warehouse_test", + state: { input: { name: "wh" }, time: { start: 1, end: 2 }, ...state }, + } as unknown as MessageV2.ToolPart +} + +const interrupted = toolPart({ status: "error", error: "Tool execution aborted", metadata: { interrupted: true } }) + +describe("SessionProcessor.shouldTrackToolError", () => { + test("a tool interrupted by the session's own abort is not tracked", () => { + expect(SessionProcessor.shouldTrackToolError(interrupted, true)).toBe(false) + }) + + test("a tool interrupted without a requested abort is still tracked", () => { + expect(SessionProcessor.shouldTrackToolError(interrupted, false)).toBe(true) + }) + + test("a genuine tool failure is tracked even after the session aborted", () => { + const failed = toolPart({ status: "error", error: "connection refused", metadata: {} }) + expect(SessionProcessor.shouldTrackToolError(failed, true)).toBe(true) + expect(SessionProcessor.shouldTrackToolError(failed, false)).toBe(true) + }) +})