Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions packages/opencode/src/altimate/telemetry/error-detail.ts
Original file line number Diff line number Diff line change
Expand Up @@ -400,6 +400,14 @@ export function describeToolError(raw: string) {
}
}

/**
* A tool part the processor closed because the session itself was aborted (user cancel) is
* not a tool failure. Interrupts the session did not request are still reported.
*/
export function isRequestedToolAbort(state: { status: string; metadata?: Record<string, any> }, sessionAborted: boolean) {
return sessionAborted && state.status === "error" && state.metadata?.interrupted === true
}

export function buildErrorFingerprintEvent(
sessionID: string,
err: {
Expand Down
2 changes: 2 additions & 0 deletions packages/opencode/src/session/prompt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2241,6 +2241,8 @@ export namespace SessionPrompt {
lastToolName = part.tool
// altimate_change end
if (toolChain.length < 50) toolChain.push(part.tool)
// altimate_change — a tool interrupted by a requested session abort is neither an error nor a recovery
if (ErrorDetail.isRequestedToolAbort(part.state, abort.aborted)) continue
const isError = part.state?.status === "error"
if (isError) {
toolErrorCount++
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import { describe, expect, test } from "bun:test"
import fs from "fs"
import path from "path"
import { ErrorDetail } from "../../src/altimate/telemetry/error-detail"

// Regression: cancelling a session while sql_execute ran reported "tool <x> aborted"
// error_fingerprint events, though the tool itself never failed.
describe("isRequestedToolAbort", () => {
const interrupted = { status: "error", error: "Tool execution aborted", metadata: { interrupted: true } }

test("interrupted part of a requested session abort is excluded", () => {
expect(ErrorDetail.isRequestedToolAbort(interrupted, true)).toBe(true)
expect(ErrorDetail.templateErrorMessage(interrupted.error)).toBe("tool <x> aborted")
})

test("interrupts the session did not request are still reported", () => {
expect(ErrorDetail.isRequestedToolAbort(interrupted, false)).toBe(false)
})

test("real tool errors during an aborted session are still reported", () => {
expect(ErrorDetail.isRequestedToolAbort({ status: "error", metadata: {} }, true)).toBe(false)
expect(ErrorDetail.isRequestedToolAbort({ status: "error" }, true)).toBe(false)
expect(ErrorDetail.isRequestedToolAbort({ status: "completed", metadata: { interrupted: true } }, true)).toBe(false)
})

test("prompt loop skips requested aborts before recording tool errors", () => {
const src = fs.readFileSync(path.join(__dirname, "../../src/session/prompt.ts"), "utf8")
const skip = src.indexOf("ErrorDetail.isRequestedToolAbort(part.state, abort.aborted)")
const record = src.indexOf('const isError = part.state?.status === "error"')
expect(skip).toBeGreaterThan(-1)
expect(record).toBeGreaterThan(skip)
})
})
Loading