Every turn failure that produces a user-facing error message must capture a Sentry exception and include the resulting event ID in the response. There should be no "event ID not available" case — if the turn fully fails, there's a Sentry event.
Current behavior
Three separate error message builders exist:
buildFailureMessage (packages/junior/src/chat/runtime/slack-runtime.ts:149) — used for top-level turn failures (handleNewMention, handleSubscribedMessage). Includes event_id and trace_id when an ErrorReference is available, but has a fallback path with no reference at all.
buildExecutionFailureMessage (packages/junior/src/chat/respond-helpers.ts:231) — used by turn-result.ts for agent-loop execution failures (empty response, escape response, raw payload leak). Never captures a Sentry exception and never includes an event ID.
- Inline fallback (
packages/junior/src/chat/slack/output.ts:352) — "I couldn't produce a response." when Slack output normalization produces empty content. No capture, no event ID.
Gap
buildExecutionFailureMessage and the output.ts fallback never call captureException, so there's no Sentry issue for these failure modes and no event ID to show the user.
buildFailureMessage has a null-reference fallback that shows a generic message with no traceability.
- There is no single function that owns the contract for "capture the failure + build the user-facing error response."
Proposed approach
- Every code path that produces a user-facing error message must first
captureException (with a real or synthetic error) so a Sentry event always exists.
- Introduce a single
buildErrorResponse function that takes an ErrorReference (never null — the caller must have captured first) and builds the user-facing message with event_id and trace_id.
- Consolidate
buildFailureMessage, buildExecutionFailureMessage, and the output.ts inline fallback to use this function.
turn-result.ts execution failures (empty model response, escape response, raw tool payload) should captureException with a descriptive synthetic error before building the response.
- Remove the no-reference fallback in
buildFailureMessage — if the reference is missing, that's a bug.
Action taken on behalf of David Cramer.
Every turn failure that produces a user-facing error message must capture a Sentry exception and include the resulting event ID in the response. There should be no "event ID not available" case — if the turn fully fails, there's a Sentry event.
Current behavior
Three separate error message builders exist:
buildFailureMessage(packages/junior/src/chat/runtime/slack-runtime.ts:149) — used for top-level turn failures (handleNewMention,handleSubscribedMessage). Includesevent_idandtrace_idwhen anErrorReferenceis available, but has a fallback path with no reference at all.buildExecutionFailureMessage(packages/junior/src/chat/respond-helpers.ts:231) — used byturn-result.tsfor agent-loop execution failures (empty response, escape response, raw payload leak). Never captures a Sentry exception and never includes an event ID.packages/junior/src/chat/slack/output.ts:352) —"I couldn't produce a response."when Slack output normalization produces empty content. No capture, no event ID.Gap
buildExecutionFailureMessageand theoutput.tsfallback never callcaptureException, so there's no Sentry issue for these failure modes and no event ID to show the user.buildFailureMessagehas a null-reference fallback that shows a generic message with no traceability.Proposed approach
captureException(with a real or synthetic error) so a Sentry event always exists.buildErrorResponsefunction that takes anErrorReference(never null — the caller must have captured first) and builds the user-facing message withevent_idandtrace_id.buildFailureMessage,buildExecutionFailureMessage, and theoutput.tsinline fallback to use this function.turn-result.tsexecution failures (empty model response, escape response, raw tool payload) shouldcaptureExceptionwith a descriptive synthetic error before building the response.buildFailureMessage— if the reference is missing, that's a bug.Action taken on behalf of David Cramer.