Skip to content

fix(sessions): explain why a session failed on the channel card#619

Merged
gbasin merged 1 commit into
masterfrom
fix/session-failure-reason-on-card
Jul 20, 2026
Merged

fix(sessions): explain why a session failed on the channel card#619
gbasin merged 1 commit into
masterfrom
fix/session-failure-reason-on-card

Conversation

@gbasin

@gbasin gbasin commented Jul 20, 2026

Copy link
Copy Markdown
Collaborator

The gap

A failed session card in a channel showed a bare "✕ Failed after 8s" with no cause.

The cause was captured — it reaches the session pane via session_recordsFailureNotice. But nothing carried it to the channel:

  • session.completed payload had only resultExcerpt, which is derived from result_textnull on failure.
  • The session row had no failure column at all, so a cold load had nothing either. (A channel's timeline query excludes thread events, so the completion event never reaches the card.)

Observed in prod: a codex run died on "You've hit your usage limit … try again at Jul 25th, 2026 3:25 AM" and the channel said only that it failed. The actionable part was one click away instead of on the card.

Change

  • Migration 084sessions.failure_class, sessions.failure_reason.
  • Server — persist both from the terminal execution_state frame; carry them on session.completed and on /sync's snapshot rows. A steer clears them (the new turn owns the outcome).
  • Shared — fold onto the client Session; mergeSessionEntity preserves them so a snapshot can't erase what the fold explained; status_changed's new-turn branch clears them.
  • Render — new failureLine() in centaur-client wraps the existing classifyFailure, preferring the engine's own words over our class summary ("You've hit your usage limit…" beats "The agent hit an error and stopped"). Wired into web + mobile MessageRow.

GET /api/sessions stays lean — these ride /sync's snapshot row only, respecting the existing "stays lean" guard.

Purely additive: with no reason reported, failureLine returns null and callers keep the existing bare "Failed". Falls back to resultText for failures logged before this.

Testing

  • pnpm check — lint + typecheck + 2811 unit tests, all green
  • pnpm e2e120 passed (4.6m)
  • New coverage: server end-to-end (frame → row + completion payload), shared fold (including steer-clears-stale-failure), and failureLine unit tests

Note

Depends on nothing, but pairs with #618 — that PR fixes the bug that produced the wrong reason on the retry; this one makes whatever reason we have visible.

A failed session card in a channel showed a bare "✕ Failed after 8s". The
cause was captured — it reaches the session pane through session_records —
but nothing carried it to the channel: the session.completed payload had
only an empty resultExcerpt (result_text is null on failure), and the
session row had no failure column at all, so a cold load had nothing to
render either (a channel's timeline query excludes thread events, so the
completion event never reaches the card).

Observed in prod: a codex run died on "You've hit your usage limit …try
again at Jul 25th" and the channel said only that it failed.

Persist failure_class + terminal_reason from the terminal execution_state
frame onto the session row, carry them on session.completed and /sync's
snapshot rows, and render them through the existing classifyFailure via a
new failureLine helper (web + mobile). A steer clears them, server-side and
in the fold, so a revived session stops explaining itself with the previous
turn's failure.

GET /api/sessions stays lean — these ride /sync's snapshot row only.
@gbasin
gbasin enabled auto-merge July 20, 2026 14:01
@gbasin
gbasin added this pull request to the merge queue Jul 20, 2026
Merged via the queue into master with commit fd0f6a5 Jul 20, 2026
18 checks passed
@gbasin
gbasin deleted the fix/session-failure-reason-on-card branch July 20, 2026 14:24
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant