Skip to content

fix(codex): auto-unarchive archived sessions before retry - #1088

Merged
SawyerHood merged 4 commits into
get-bb:mainfrom
patleeman:agent/auto-unarchive-codex-sessions
Aug 9, 2026
Merged

fix(codex): auto-unarchive archived sessions before retry#1088
SawyerHood merged 4 commits into
get-bb:mainfrom
patleeman:agent/auto-unarchive-codex-sessions

Conversation

@patleeman

@patleeman patleeman commented Aug 6, 2026

Copy link
Copy Markdown
Contributor

Why this is needed

bb reaps idle thread-scoped Codex processes after 30 minutes and later resumes them by provider thread ID. The persisted Codex session can also be archived independently, for example when its thread is archived in Codex Desktop. If bb later sends thread/resume, turn/start, or turn/steer for that provider thread, Codex rejects the request, and bb surfaces the failure, leaving the user to unarchive the session manually before trying again.

What changed

The runtime now recognizes Codex's archived-session error, sends thread/unarchive for the recorded provider thread, and retries the original request once. Recovery is limited to Codex and to that specific error, so other providers and other failures keep their existing behavior.

The recovery uses the existing command path for resume, turn start, and steer, including resume during thread reconfiguration. Turn-start bookkeeping still rolls back correctly if either the initial request or the retry fails.

Persisted Codex threads now start with ephemeral: false. This lets Codex materialize the rollout on disk so bb can resume it after reaping the provider process. The obsolete persistExtendedHistory field is no longer sent on start or resume.

Validation

The regression test reuses the existing fake provider and makes it return the archived-session error until bb sends thread/unarchive. This covers the recovery path without adding a separate test process or harness.

  • pnpm exec turbo run test --filter=@bb/agent-runtime — 811 tests passed
  • pnpm exec turbo run test --filter=@bb/host-daemon — 479 tests passed
  • pnpm exec turbo run typecheck --filter=@bb/agent-runtime --filter=@bb/host-daemon — passed
  • Prettier check and git diff --check — passed

Live verification

I also ran the source dev app from this branch with the real Codex CLI. I created a disposable thread, confirmed the initial Codex response, and archived only its underlying provider session with codex archive. I then restarted the dev host daemon to recreate the provider process and sent a normal follow-up through pnpm bb:dev thread tell --mode auto.

The follow-up completed successfully and returned BB_LIVE_RESTARTED_RECOVERY_OK. The Codex rollout record moved from its archived-session store back into the active session store, confirming that bb unarchived the provider session before retrying.

This remains a draft for maintainer review.

@patleeman
patleeman marked this pull request as ready for review August 6, 2026 21:49
@SawyerHood

Copy link
Copy Markdown
Collaborator

🚨 SLOP COP 🚨 · review

I am the SlopCop. I am reviewing this pull request for security, code quality, performance, architecture, and end-to-end behavior.

I will use the review rule guidelines. I will post one final review after the parallel checks finish.

Comment thread packages/agent-runtime/src/runtime.ts Outdated
commandType: "thread/unarchive",
...recovery,
});
return sendJsonRpcRequest(request);

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 slopcop/review — The retry can use a terminated provider process.

This request stores the original child process and request table. The recovery can start a replacement process.

The retry still sends to the original child. A closed input drops the write, so the request waits for the timeout.

Resolve the current process after recovery. Otherwise, fail quickly when the original process changed. Add a process-exit test.

message: SendJsonRpcRequestArgs<TResult>["message"];
resultSchema: SendJsonRpcRequestArgs<TResult>["resultSchema"];
timeoutMs?: number;
recovery?: CodexArchivedSessionRecoveryArgs;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 slopcop/review — The fork path does not use this recovery.

The startThread path also sends thread/fork. Its call at line 1149 does not supply recovery data.

Thus, an archived source can still make a fork or side chat fail. Add fork recovery and a focused test.

@SawyerHood SawyerHood left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 SLOP COP 🚨 · review

ELI5

BB opens an archived Codex conversation and retries the action. Two paths can still fail during that recovery.

Findings

  • P2: A provider exit can make the retry use the terminated process. The request can then wait 30 seconds and fail.
  • P2: The thread/fork path does not pass recovery data. A fork from an archived source can still fail.
  • P3: The fork adapter still sends persistExtendedHistory. Start and resume remove this stale field, and generated contracts omit it.

I would fix both P2 findings before merge. I would also align the fork persistence contract with the other creation paths.

Security and performance

I found no confirmed security issue. The provider has full local trust, and the error text does not select the target identifier.

Normal recovery adds one unarchive request and one retry. This work has constant cost.

Validation

  • git diff --check passed.
  • GitHub reports no CI checks for this head.
  • I did not run code from the untrusted fork.
  • A browser test does not apply because this runtime change has no browser flow.
  • Add process tests for provider exit, fork, resume, and steer.

This is a comment-only review. I did not approve the pull request or request changes.

Address review feedback on the archived-session recovery.

- Recover `thread/fork` too. A fork reads its source session, so an
  archived source failed the same way a resume did.
- Resolve the provider process again before the retry. Unarchiving can
  replace a dead process, and the retry wrote to the old child's stdin.
- Keep the archived-session error when unarchiving fails. That message
  names the session and the CLI command that fixes it.
- Drop the stale `persistExtendedHistory` field from `thread/fork`.
  Codex does not know the field, and start and resume already dropped it.
- Correct the `ephemeral` comment. Codex already defaults to
  non-ephemeral, so the field pins the value; it does not change it.

Add runtime tests for the resume, fork, and failed-unarchive paths, and
assert the fork adapter sends no `persistExtendedHistory`.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@SawyerHood

Copy link
Copy Markdown
Collaborator

🚨 slopcop/review — I am SlopCop. I am reviewing this pull request for security, quality, performance, architecture, and test coverage.

I will post the combined findings after the parallel reviews finish.

Comment thread packages/agent-runtime/src/runtime.ts Outdated

// Unarchiving can replace a dead provider process, so resolve the
// process again instead of writing to the captured child's stdin.
const retryProc = requireProviderProcess({

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 slopcop/review — The recovery stops a replacement process before the retry.

If the original Codex process exits, the exit handler clears its thread identity and configuration. Recovery then starts a replacement with no registered thread.

The idle cleanup at line 789 stops that replacement. This lookup then throws Provider "codex" is not running.

Keep the recovery process alive and attach the thread before the retry. Add a test where the fake provider reports the archive error and exits.

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed the part I can demonstrate, and dropped the part I cannot.

Fixed. The recovery now reports the archived-session error whenever the recovery itself cannot run, with the underlying failure attached as cause. That message names the session and the codex unarchive command that fixes it. Added a test where the fake provider reports the archived error and then exits, per your suggestion.

Not fixed, deliberately. I did not add a guard to keep the replacement process alive. I could not write a test that distinguishes it, for two reasons:

  1. In the interleaving the test actually produces, the exit handler has not run yet when recovery starts. ensureProvider therefore finds the old process still in its map and returns it, so the thread/unarchive goes to a dead stdin and is rejected by the exit handler. The idle cleanup is never reached.
  2. In the interleaving you describe, where the exit handler has run first, the guard would keep the replacement alive — but I cannot schedule that interleaving from a test, and a turn/start retry against a fresh process would fail anyway because the thread is not loaded there.

The user-visible guarantee holds in both cases now: the caller gets the archived-session error, never a bare Provider "codex" has exited. I preferred that over shipping a defensive branch with no test behind it.

The deeper issue — ensureProvider handing back a process whose child has exited but whose exit event has not fired — is pre-existing and affects every in-flight command, not just this recovery path. It belongs in its own change.

@SawyerHood SawyerHood left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🚨 SLOP COP 🚨 · review

ELI5

BB reopens an archived Codex session and retries the action. A provider restart can make BB close the new helper before the retry.

Finding

  • P2: Recovery can stop the replacement process before the retry uses it.

A provider exit clears the old thread identity and configuration. Recovery starts a replacement process with no registered thread.

The idle cleanup stops that replacement. The next process lookup then throws Provider "codex" is not running.

Keep the replacement alive and attach the thread before the retry. Add a test where the fake provider reports the archive error and exits.

Other checks

I found no security issue. The provider error text does not control a command or select a different session.

The normal recovery path has constant cost. I found no other performance or concurrency issue.

The central recovery helper fits the current architecture. I found no material duplication or stale production use of persistExtendedHistory.

I did not start a browser test because this runtime change has no browser route.

Validation

  • The @bb/agent-runtime suite passed all 814 tests.
  • The Turbo type check passed.
  • git diff --check passed.
  • All GitHub checks passed on the exact head.
  • The final GPT-5.6 review confirmed the P2 finding.

This review only adds comments. I did not approve the pull request or request changes.

Address the SlopCop finding about a provider exit during recovery.

A provider that exits while bb unarchives cannot be unarchived or
retried. Report the archived-session error in that case, and attach the
recovery failure as the cause. That message names the session and the
CLI command that fixes it. A process-level error such as
`Provider "codex" has exited` tells the user nothing actionable.

Add a test where the fake provider reports the archived error and then
exits.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@SawyerHood
SawyerHood merged commit a4e3011 into get-bb:main Aug 9, 2026
9 checks passed
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.

2 participants