fix(server): preserve the crash recovery budget across restarts#25602

open1
main
maria/takeover-pr-24177
opened2d agobyMmaria
View on GitHubTwo-way sync enabledPR last imported 18h ago

Problem

The automatic restart guard forgets older crashes after five minutes, so an unfinished turn can keep restarting the server indefinitely. This preserves the existing three-recovery limit across unplanned restarts.

Change

Persist the cumulative process-loss count on the existing continuation intent, forward it through the worker, and decline the fourth recovery. Planned shutdowns and provider-completed turns reset the allowance; live continuations retain the predecessor intent past outbox cleanup. Legacy intents retain their existing guard.

Compared with #24177, this omits environment-wide admission, child-first scheduling, parent-wake suppression, their migration, and policy-specific tests/docs. Those are separate policy decisions. The pruning lookup uses the existing thread/status index. No UI or client contract changes.

Scope and approval

Addresses #24059. Maria explicitly requested this takeover and a smaller fix in the task brief. This keeps the existing three-recovery allowance and fixes its elapsed-time loophole; the environment-wide admission and parent-wake policies are excluded. The task authorization is not a public approval comment. Macroscope requests human approval of the remaining stateful recovery behavior before merge.

Verification

  • Before: nine focused regressions failed on current main (220eb73524ca). After: all 111 tests in the three focused suites passed.
  • Blacksmith vp run check:changed passed format, lint, both knip checks, generated GraphQL, server/scripts typechecks, and 125 tests in five files.
  • Live Codex + authenticated WebSocket RPC: four forced backend restarts persisted counts 1–4, resumed only the first three, and declined the fourth. The fourth recovery was also declined after a 310-second live pause. Manual resume and Stop passed, including with recovery disabled. Planned shutdown reset a saved count of 2 to 0; the next crash resumed at 1 and the provider completed with its expected response.
  • Runtime transcript and verification limits.
  • Two independent GPT-6.1-Sol reviews returned merge-ready with no findings on 0475b3da8e.
  • Unverified: other providers, desktop/mobile, remote/relay/tunnel modes. The 22-minute gap is tested with controlled timestamps rather than a wall-clock live wait. All 28 hosted checks are green, including build/typechecks, lint, all test suites, and CodeRabbit (no actionable findings). Macroscope requires human approval of the recovery policy.

This bounds successive recovery runs. Crashes before a successor run commits, child-result parent wakes, host overload, and orphaned subprocesses remain outside scope. Historical crashes without a saved count are not reconstructed.

Takeover

Replaces #24177, with thanks to @Zheruel for the original implementation.

Built with GPT-6.1-Sol in the Codex harness on T3 Code.

Checks

20 successful · 7 skipped · 1 neutral
All checks passed
0475b3d

Added 1 commit

  • fix(server): preserve the crash recovery budget across restarts0475b3d
Synced from GitHub

Approvability

Verdict: Needs Human Approval

Macroscope's review found this PR requires human approval — The PR changes production crash-recovery policy across restarts, persisting a cumulative loss count and suppressing the fourth recovery of an unfinished turn. Because this gate determines whether an existing provider workflow resumes, the stateful behavior warrants human review.

You can add or adjust custom eligibility rules. Learn more.

Synced from GitHub

Thread transfer impact

✅ Thread transfer remains within every enforced ceiling.

ℹ️ The exact PR base did not have a successful artifact. Baseline uses the latest successful main measurement shown below.

ProviderMetricMain baselineThis PRImpactPR ceiling
CodexTotal thread wire5.0 KiB5.0 KiB+7 B (+0.1%)6.8 KiB✅
CodexThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
CodexLive turn WebSocket wire1.2 KiB1.2 KiB+7 B (+0.6%)2.0 KiB✅
CodexLive turn WebSocket decoded20.9 KiB20.9 KiB0 B (0.0%)29.3 KiB✅
CodexLive turn messages220 (0.0%)8✅
ClaudeTotal thread wire5.0 KiB5.0 KiB0 B (0.0%)6.8 KiB✅
ClaudeThread snapshot wire3.8 KiB3.8 KiB0 B (0.0%)4.9 KiB✅
ClaudeLive turn WebSocket wire1.2 KiB1.2 KiB0 B (0.0%)2.0 KiB✅
ClaudeLive turn WebSocket decoded21.2 KiB21.2 KiB0 B (0.0%)29.3 KiB✅
ClaudeLive turn messages220 (0.0%)8✅

Baseline: 4e42eaf · PR result: 0475b3d · Source CI: success

Scenario and decoded snapshot size

10 historical turns, 5 command tools per turn, 878.9 KiB retained MCP result per historical turn, and a 1.05 MiB retained result in the measured turn.

  • Codex decoded thread snapshot: 108.5 KiB
  • Claude decoded thread snapshot: 108.8 KiB

Updated in place by a trusted workflow. PR artifacts are strictly validated and never executed.

Synced from GitHub

Review in Change Stack →

No actionable comments were generated in the recent review.

External image Recent review info
External image Run configuration
External image Commits

Reviewing files that changed from the base of the PR and between 220eb73524ca7882d53b6a12c824ccbcd97ad279 and 0475b3da8e27d0e8667f8955978642c6742201f3.

External image Files selected for processing: 9
  • apps/server/src/orchestration-v2/EffectOutbox.ts
  • apps/server/src/orchestration-v2/EffectWorker.test.ts
  • apps/server/src/orchestration-v2/EffectWorker.ts
  • apps/server/src/orchestration-v2/FoundationPersistence.test.ts
  • apps/server/src/orchestration-v2/ProviderRuntimeRecoveryService.ts
  • apps/server/src/orchestration-v2/RestartContinuation.test.ts
  • apps/server/src/orchestration-v2/RestartContinuation.ts
  • docs/internals/server-updates.md
  • docs/user/updating.md

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 0 remain after this review.

Walkthrough

Restart continuations now carry a cumulative process-loss count. Recovery increments or resets the count based on run state, and delivery applies a count-based limit. Outbox pruning retains continuation effects while matching runs remain active. Tests and documentation cover these rules.

Changes: Restart continuation recovery

Layer / File(s)Summary
Continuation intent and retention
apps/server/src/orchestration-v2/EffectOutbox.ts, apps/server/src/orchestration-v2/FoundationPersistence.test.ts
Continuation requests can include a nonnegative processLossCount. Pruning retains settled continuation effects while a matching run is queued, preparing, starting, running, or waiting.
Recovery count and delivery guard
apps/server/src/orchestration-v2/ProviderRuntimeRecoveryService.ts, apps/server/src/orchestration-v2/EffectWorker.ts, apps/server/src/orchestration-v2/RestartContinuation.ts, apps/server/src/orchestration-v2/*test.ts, docs/internals/server-updates.md, docs/user/updating.md
Recovery increments the count for unfinished work and resets it for completed or waiting work. Shutdown continuations use zero. The worker passes the count to continueRestartedRun, which applies the count-based limit; legacy requests without a count retain the chain-length check.

Priority: External image Normal

Estimated code review effort: 3 (Moderate) | ~25 minutes

Change: Bug fix

Sequence Diagram(s)

mermaid
1sequenceDiagram
2 participant ProviderRuntimeRecoveryService
3 participant EffectOutbox
4 participant EffectWorker
5 participant RestartContinuation
6 ProviderRuntimeRecoveryService->>EffectOutbox: Read prior continuation effect
7 EffectOutbox-->>ProviderRuntimeRecoveryService: Return retained processLossCount
8 ProviderRuntimeRecoveryService->>EffectWorker: Submit continuation with updated count
9 EffectWorker->>RestartContinuation: Call continueRestartedRun with count
10 RestartContinuation-->>EffectWorker: Apply continuation limit

Possibly related PRs

  • pingdotgg/t3code#24177: Both changes persist and forward processLossCount and update restart-continuation recovery behavior.

Suggested reviewers: t3dotgg, yash-singh1

Merge Risk: External image Minimal · up to 0475b

The restart-recovery limit has no identified merge-blocking issue; merge after normal checks.

Pre-merge checks: Passed 3 · Failed 1

Failed Failed checks: 1 warning

Check nameStatusExplanationResolution
Description checkWarningThe description clearly covers the problem, change, scope, exclusions, verification results, limitations, and tooling. The Scope and approval section links issue #24059, but it does not provide the re…Add a link to the maintainer approval comment that explicitly approves the direction and scope. If no prior approval exists, explain why this focused fix qualifies for the exception and provide the required maintainer approval before merge.
Passed Passed checks: 3 passed
Check nameStatusExplanation
Title checkPassedThe title "fix(server): preserve the crash recovery budget across restarts" follows conventional commit format and matches the main change. The PR persists a cumulative process-loss count on the conti…
Linked Issues checkPassedCheck skipped because no linked issues were found for this pull request.
Out of Scope Changes checkPassedCheck skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title "fix(server): preserve the crash recovery budget across restarts" follows conventional commit format and matches the main change. The PR persists a cumulative process-loss count on the continuation intent so the three-recovery limit survives unplanned restarts, and declines the fourth recovery. The title is specific and concise, and it names the server scope.

Full details: Description check

Explanation

The description clearly covers the problem, change, scope, exclusions, verification results, limitations, and tooling. The Scope and approval section links issue #24059, but it does not provide the required public maintainer approval comment; it explicitly states that the task authorization is not public approval.

Resolution

Add a link to the maintainer approval comment that explicitly approves the direction and scope. If no prior approval exists, explain why this focused fix qualifies for the exception and provide the required maintainer approval before merge.

Finishing Touches
📝 Generate docstrings
  • Commit to this branch
  • Create a new PR
🧪 Generate unit tests (beta)
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Comment @coderabbitai help to get the list of available commands.

Note

Written by gpt-6.1-sol on behalf of Maria

Verified in a real dev environment

verified as-is at 0475b3da8e27d0e8667f8955978642c6742201f3 on a linux boat vm. no code changes.

  • reproduced on freshly fetched github/main (48a292364a) in a separate worktree. after three automatic recoveries, waited over 326 seconds before the fourth crash. main incorrectly started another real codex run.
  • repeated through the pr's web composer using repo playwright, /usr/bin/google-chrome, a project added through the app pointing at a real git repo, and real codex gpt-6.1-sol turns. no mocks or database/timestamp injection.
  • the pr persisted counts 1, 2, and 3 and resumed each time. after a live wait over 328 seconds, the fourth crash persisted count 4, logged the recovery-limit warning, and created no automatic successor. a new user message then read the repo's readme and completed with MANUAL_RESUME_OK.
  • neighboring behavior passed: stop, the resume-thread control, a completed turn starting a fresh recovery sequence, and graceful shutdown resetting count 2 to 0. the next crash used count 1, resumed, and completed with RESET_RECOVERY_COMPLETE.
  • blacksmith: 111 focused tests passed. vp run check:changed against the pr base passed format, lint, both knip checks, generated graphql, affected typechecks, and 125 related tests. hosted checks: 28/28 green.

main after the fourth crash, still running:

main incorrectly resumes a fourth time after the five-minute window

pr after the fourth crash, stopped after three recoveries:

pr stays idle after its fourth crash with only three automatic resumes

new user message after the cap, real terminal read, and provider completion (20 seconds):

manual continuation after the recovery cap completes through codex

command transcript, persisted counts, and verification limits

Not verified

native desktop/iOS/Android, macOS/Windows, other providers, and remote/relay/tunnel connections were not exercised on this linux vm. week-long outbox retention and completed/waiting background-work recovery were covered by existing tests, not live waits.

an initial root launch selected the old runner's default home; it was stopped and excluded, then the pr scenario was recreated with an explicit isolated home. one recording attempt failed during vm cpu overload (a 19-second freeze notification); the replacement recording was inspected and its uploaded playback and seeking were verified in t3 preview. browser reloads recovered; no ten-minute provider/app stall occurred.