You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Codex 0.146+ holds a rollout writer lock for the lifetime of each app-server process, but the Codex bridge acknowledged release and spawned replacement children immediately after sending SIGTERM. A settings rebuild, stop/resume, bridge restart, or environment move could therefore resume while bb's previous child still owned the rollout.
The first implementation awaited child exit, but review found two more bridge-local windows. Reconstruction initially removed the old map entry before awaiting exit, allowing a concurrent stop or release-after-maintenance request to miss the in-flight replacement. Publishing the replacement closed that gap, but the pending old-child exit still lived only on the constructor stack: concurrent release found the connection-less replacement and acknowledged before the old child released its writer lock.
What changed
Make Codex child termination awaitable through finalized process exit, including the existing SIGKILL and close-grace bounds.
Await release before rebuilding, answering thread/stop, and completing maintenance operations that release a session.
Publish a connection-less replacement before waiting for the old child, and park the old child's exit promise on it. Concurrent stop/discard now marks the replacement closing and waits for both the parked old exit and any current child exit before acknowledging; reconstruction re-checks closing before spawning.
Retry transient active-writer construction refusals on a bounded 100/400/1000 ms ladder and log every retry with its method, delay, and attempt number.
Improve persistent-writer guidance to cover both another visible Codex session and a previous/leftover codex app-server process.
Add deterministic regressions for settings rebuild, response-time stop serialization, concurrent stop and discard during reconstruction, transient ownership, and persistent ownership recovery. Shared process-test cleanup is missing-log-safe and attempts every cleanup stage even if an earlier one fails.
Keep recovery provider-local. Independent real-Codex reproduction showed that the same losing app-server can retry resume after the owner exits, so no new wire recovery kind is needed. Native late-item ordering is orthogonal to the process-lifetime writer lock.
No host-daemon wire contract changed, so this PR does not increment HOST_DAEMON_PROTOCOL_VERSION beyond the version already on main.
This PR fixes bb-owned writer handoff races. A persistent writer owned by an independent Codex process is improved, not eliminated: after bounded retries it still returns an untyped bridge error, now with retry diagnostics and actionable guidance.
How you verified
The regressions were proven red against the exact behaviors they guard:
Mutating the stop path back to fire-and-forget makes the response-time lock assertion fail with expected true to be false.
Restoring the old map-before-await gap makes both concurrent release tests fail because reconstruction succeeds after release was already acknowledged.
Before parking the old-child exit on the published replacement, the concurrent stop and concurrent discard tests both acknowledged while writer.lock still existed; both immediate lock-absence assertions failed deterministically with expected true to be false on two runs.
Green checks on the final tree:
pnpm exec turbo run test --filter=bb-plugin-provider-codex --force -- src/bridge/bridge.writer-lock.test.ts src/bridge/bridge.archived-rebuild.test.ts (2 files, 8/8 passed)
pnpm exec turbo run test --filter=bb-plugin-provider-codex --force (25 files, 255/255 passed)
pnpm exec turbo run test --filter=@bb/agent-runtime --force (22 files, 318/318 passed)
pnpm exec turbo run typecheck --filter=bb-plugin-provider-codex --force
pnpm exec turbo run build --filter=@bb/host-daemon --force
pnpm exec oxlint and pnpm exec oxfmt --check on every changed TypeScript file
Real Codex 0.148.0 scratch smoke: app-server B received the native active-writer refusal while A held the rollout, then the same B process resumed successfully after A exited.
Rebased these three commits onto current main (author preserved) in #3460 and added the missing piece on the daemon side.
While reproducing #2327 on a live machine, the active-writer refusal turned out to start in the host daemon, not in the bridge: emitTranslatedEvents bound the delta-assembled thread/identity (empty thread id) to a stale pending-identity slot, so after a message edit the next thread/start on the same bridge recorded its Codex thread id onto the rewound thread, and both bb threads then resumed one rollout. #3460 fixes that binding, refuses to resume a provider thread another hosted thread owns, and carries this PR's retry ladder, guidance, writer.lock fixture, and writer-lock tests. The handoff serialization in constructThreadSession / releaseSession already landed via #3025, so main's implementation is kept there.
Leaving this PR open for you or a maintainer to close once #3460 is reviewed.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Human comments
What was wrong
Codex 0.146+ holds a rollout writer lock for the lifetime of each app-server process, but the Codex bridge acknowledged release and spawned replacement children immediately after sending SIGTERM. A settings rebuild, stop/resume, bridge restart, or environment move could therefore resume while bb's previous child still owned the rollout.
The first implementation awaited child exit, but review found two more bridge-local windows. Reconstruction initially removed the old map entry before awaiting exit, allowing a concurrent stop or release-after-maintenance request to miss the in-flight replacement. Publishing the replacement closed that gap, but the pending old-child exit still lived only on the constructor stack: concurrent release found the connection-less replacement and acknowledged before the old child released its writer lock.
What changed
thread/stop, and completing maintenance operations that release a session.codex app-serverprocess.HOST_DAEMON_PROTOCOL_VERSIONbeyond the version already onmain.This PR fixes bb-owned writer handoff races. A persistent writer owned by an independent Codex process is improved, not eliminated: after bounded retries it still returns an untyped bridge error, now with retry diagnostics and actionable guidance.
How you verified
The regressions were proven red against the exact behaviors they guard:
expected true to be false.writer.lockstill existed; both immediate lock-absence assertions failed deterministically withexpected true to be falseon two runs.Green checks on the final tree:
pnpm exec turbo run test --filter=bb-plugin-provider-codex --force -- src/bridge/bridge.writer-lock.test.ts src/bridge/bridge.archived-rebuild.test.ts(2 files, 8/8 passed)pnpm exec turbo run test --filter=bb-plugin-provider-codex --force(25 files, 255/255 passed)pnpm exec turbo run test --filter=@bb/agent-runtime --force(22 files, 318/318 passed)pnpm exec turbo run typecheck --filter=bb-plugin-provider-codex --forcepnpm exec turbo run build --filter=@bb/host-daemon --forcepnpm exec oxlintandpnpm exec oxfmt --checkon every changed TypeScript fileFixes #2327