This was generated by AI during triage.
Summary
tool_call telemetry currently derives outcome from tool output text. This makes some cancellation paths show up as error, and can also make real errors show up as cancelled when their output happens to contain cancellation-related words.
I can help implement the fix, but I would like to discuss the preferred shape with maintainers first because the robust fix may involve structured metadata on internal tool.result handling, and possibly SDK-visible event contracts.
Affected code
packages/agent-core/src/agent/turn/index.ts: telemetryToolOutcome classifies by string matching against tool output.
packages/agent-core/src/tools/builtin/shell/bash.ts: Bash abort and timeout paths return user-visible text such as Interrupted by user and Command killed by timeout (...).
packages/agent-core/src/loop/tool-call.ts: isUserCancellation(signal.reason) is available while settling aborted tool calls, but this structured signal is not preserved for telemetry.
What I observed
Current classification logic treats an error result as cancelled only when output contains one of:
aborted
cancelled
manually interrupted
That means these cases are currently misclassified or fragile:
- Bash user abort returns
Interrupted by user, so telemetry records outcome: error instead of cancelled.
- Bash timeout returns
Command killed by timeout (...), so it is also recorded as error; whether timeout should remain error or become a separate category seems worth deciding explicitly.
- A real hook/tool error whose message or stack contains
aborted, cancelled, or manually interrupted can be recorded as cancelled, which also suppresses the error_type field because error_type is only attached when outcome === "error".
Expected behavior
Telemetry should not depend only on user-visible output strings. User-initiated cancellation should be classified consistently as cancelled; real tool or hook failures should remain error; timeout behavior should have an explicit agreed meaning, either a separate outcome or an error subtype.
Possible fix directions
- Minimal fix: add another string such as
interrupted to telemetryToolOutcome. This covers the Bash user-abort text but keeps the false-positive problem and remains coupled to copy changes.
- Preferred direction: preserve structured outcome or reason metadata when creating
tool.result events or internal telemetry inputs. The loop already has access to isUserCancellation(signal.reason) in cancellation paths, so telemetry can consume structured state instead of guessing from output.
I am happy to help with the implementation after maintainers confirm which telemetry schema and event-surface tradeoff is preferred.
Additional notes
Impact appears limited to telemetry and the distribution of error_type; I did not find in-process retry, UI, or control-flow logic depending on this telemetry outcome field.
Summary
tool_calltelemetry currently derivesoutcomefrom tool output text. This makes some cancellation paths show up aserror, and can also make real errors show up ascancelledwhen their output happens to contain cancellation-related words.I can help implement the fix, but I would like to discuss the preferred shape with maintainers first because the robust fix may involve structured metadata on internal
tool.resulthandling, and possibly SDK-visible event contracts.Affected code
packages/agent-core/src/agent/turn/index.ts:telemetryToolOutcomeclassifies by string matching against tool output.packages/agent-core/src/tools/builtin/shell/bash.ts: Bash abort and timeout paths return user-visible text such asInterrupted by userandCommand killed by timeout (...).packages/agent-core/src/loop/tool-call.ts:isUserCancellation(signal.reason)is available while settling aborted tool calls, but this structured signal is not preserved for telemetry.What I observed
Current classification logic treats an error result as
cancelledonly when output contains one of:abortedcancelledmanually interruptedThat means these cases are currently misclassified or fragile:
Interrupted by user, so telemetry recordsoutcome: errorinstead ofcancelled.Command killed by timeout (...), so it is also recorded aserror; whether timeout should remainerroror become a separate category seems worth deciding explicitly.aborted,cancelled, ormanually interruptedcan be recorded ascancelled, which also suppresses theerror_typefield becauseerror_typeis only attached whenoutcome === "error".Expected behavior
Telemetry should not depend only on user-visible output strings. User-initiated cancellation should be classified consistently as
cancelled; real tool or hook failures should remainerror; timeout behavior should have an explicit agreed meaning, either a separate outcome or an error subtype.Possible fix directions
interruptedtotelemetryToolOutcome. This covers the Bash user-abort text but keeps the false-positive problem and remains coupled to copy changes.tool.resultevents or internal telemetry inputs. The loop already has access toisUserCancellation(signal.reason)in cancellation paths, so telemetry can consume structured state instead of guessing fromoutput.I am happy to help with the implementation after maintainers confirm which telemetry schema and event-surface tradeoff is preferred.
Additional notes
Impact appears limited to telemetry and the distribution of
error_type; I did not find in-process retry, UI, or control-flow logic depending on this telemetryoutcomefield.