Skip to content

fix(sessions): stop agent-crash recovery from wiping task history#3172

Open
richardsolomou wants to merge 1 commit into
mainfrom
posthog-code/fix-session-recovery-history-loss
Open

fix(sessions): stop agent-crash recovery from wiping task history#3172
richardsolomou wants to merge 1 commit into
mainfrom
posthog-code/fix-session-recovery-history-loss

Conversation

@richardsolomou

Copy link
Copy Markdown
Member

Problem

When the Claude agent process died at the wrong moment, the local-session recovery path could permanently wipe the task's history and leave the task unrecoverable (reported: "the agent just died and it stopped the task… it actually cleared out the entire history of the task itself"). Three compounding issues:

  1. Retry destroyed the run. clearSessionError recreated the session whenever initialPrompt was still set. That flag is only cleared by a live agent event, so it survives a crash (or a dropped event subscription) before the first event reaches the renderer — even when the agent already did real, logged work. The recreate branch tore the session down and called createTaskRun, orphaning the populated run log behind a fresh empty latest_run; the auto-retry loop could repeat this up to three times.
  2. Reconnect blanked the transcript. reconnectToLocalSession repaints the session from the run log, and fetchSessionLogs swallows read errors into an empty result — so an unreadable log after a crash replaced a fully populated in-memory transcript with an empty one.
  3. API timeouts entered recovery at all. Transient upstream provider failures ("API Error: … the operation timed out / terminated / Connection error / 5xx") arrive wrapped as "Internal error: …", so isFatalSessionError's generic "internal error" pattern classified them as fatal and sent a healthy session through the same teardown/recovery machinery.

Changes

  • clearSessionError only recreates the run when the conversation genuinely never started: a new runHasConversationHistory helper checks the in-memory transcript, then the persisted run log, for any event beyond the user's own prompt echo. Otherwise it reconnects in place, preserving the run and its history.
  • reconnectToLocalSession keeps the previous in-memory events when the log read produces nothing instead of overwriting them with an empty array.
  • New isTransientUpstreamError in @posthog/shared (patterns kept in sync with the agent's classifyAgentError); isFatalSessionError now returns false for these. On a transient failure the prompt handler skips recovery, clears the pending-prompt state, and surfaces "The AI provider timed out or dropped the connection. Your session is unaffected — please send the message again." instead of the raw internal error.

How did you test this?

  • New unit tests: recreate-vs-reconnect gating in sessionServiceRetryConfig.test.ts (in-memory history, log-only history, prompt-echo-only), transcript preservation on empty log read and no-recovery-on-API-timeout in sessionServiceHost.test.ts, and 12 classification cases in errors.test.ts.
  • Full suites green: shared (48), core sessions (263), UI host (135). typecheck clean on shared/core/ui; Biome clean with zero noRestrictedImports in core.

Automatic notifications

  • Publish to changelog?
  • Alert Sales and Marketing teams?

🤖 Generated with Claude Code

When the agent process died at the wrong moment, the recovery path could
destroy the task's entire transcript:

- clearSessionError recreated the run whenever initialPrompt was still
  set, but that flag is only cleared by a live agent event — it survives
  a crash (or dropped subscription) before the first event, even when
  the run log already holds real work. Teardown + createTaskRun then
  orphaned the populated log behind a fresh empty latest_run, and each
  auto-retry repeated it. Recreation is now gated on the run having no
  conversation history (in memory or in the run log).

- reconnectToLocalSession repainted the session from the log, and
  fetchSessionLogs swallows read errors into an empty result — so a
  failed read after a crash blanked a transcript we already held. The
  previous in-memory events are now kept when the log read is empty.

- Upstream provider failures ("API Error: … timed out / terminated /
  Connection error / 5xx") were pattern-matched as fatal session errors
  via their "Internal error: …" wrapper, sending a healthy session into
  the same teardown/recovery machinery. They're now classified as
  transient: no recovery runs, and the user is told to just re-send.

Generated-By: PostHog Code
Task-Id: cb0f3882-78ec-41bc-8ad3-9916bb7734c7
@trunk-io

trunk-io Bot commented Jul 6, 2026

Copy link
Copy Markdown

Merging to main in this repository is managed by Trunk.

  • To merge this pull request, check the box to the left or comment /trunk merge below.

After your PR is submitted to the merge queue, this comment will be automatically updated with its status. If the PR fails, failure details will also be posted here

@github-actions

github-actions Bot commented Jul 6, 2026

Copy link
Copy Markdown

React Doctor found no issues in the changed files. 🎉

Reviewed by React Doctor for commit 8accc8b.

@greptile-apps

greptile-apps Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

Reviews (1): Last reviewed commit: "fix(sessions): stop agent-crash recovery..." | Re-trigger Greptile

@richardsolomou richardsolomou requested a review from a team July 6, 2026 11:23
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