Skip to content

fix: Codex account switch releases the conversation, Reset usage on the limit banner - #82

Merged
ScriptedAlchemy merged 8 commits into
mainfrom
fix/codex-account-switch-and-reset-usage
Nov 9, 2026
Merged

ScriptedAlchemy merged 8 commits into
mainfrom
fix/codex-account-switch-and-reset-usage

Conversation

@ScriptedAlchemy

Copy link
Copy Markdown
Owner

Two phone-reported bugs, plus the root cause of why quitting the app was the only workaround.

1. Switching Codex accounts stranded the conversation

Hitting the usage limit on one Codex account, switching the thread to another Codex account, and sending failed with "This conversation is open in another Codex client" — forever, until the desktop app was quit.

Root causes, from the server logs and traces of the failed thread:

  • ProviderSessionManager.detach only unloaded native threads still bound to the old session in the projection. An account switch rebinds the thread to the new account's session in the same commit, so the main conversation was never unsubscribed on the old account's app-server (only a subagent's was), and that process held Codex's writer lock indefinitely. Quitting the app killed the process, which is why quit-and-reopen "worked".
  • Even after thread/unsubscribe, Codex holds the lock until its idle sweep emits thread/closed about 60s later, while turn-start retries span ~1.5s.

Fix: detach also unloads this thread's same-driver native threads that were rebound to another session, taking the driver from the live runtime (the detached event has already removed the session's projection binding, so the projection cannot supply it). A process-wide release registry records threads this server unsubscribed; a resume refused for one of those waits up to 90s for thread/closed (or app-server exit) and retries once. Threads held by genuinely external clients (Codex app/CLI/IDE) still fail immediately with the same message. Expect the first message after a switch to pause up to ~a minute instead of erroring.

2. No way to reset usage from the limit banner

The usage-limit banner/card only offered "Resume at reset" and "Snooze until reset", though the usage panel could already spend a banked reset credit. When the limited account reports banked reset credits, the banner (web/desktop) and card (iOS/Android) now offer Reset usage: confirm, redeem via the existing consumeResetCredit command, then send to continue. It appears even when no reset time is known and respects the providers-manage scope. Mobile's redemption hook moves to usage/useResetCredit.ts so panel and card share it.

Proof

  • New regression tests for both server fixes; each fails on the old code (the detach test replays the real commit ordering, detached event included, and asserts the rebound root thread is unloaded) and passes with the fix.
  • vp run check:changed passes: format, lint, knip, typecheck (41 packages), tests beside the changes, React compiler. Server suites: 289 passed.
  • Not exercised in a live client; no reset credit was redeemed. The banner change is covered by its unit tests and the server behavior by the replay/manager tests.

Model: muse-wonder-1.0-contributor (Muse Code)

Detach only unloaded native threads still bound to the old session, but
an account switch rebinds the thread to the new account's session in the
same commit (and removes the old session's projection binding), so the
root conversation was never unsubscribed and the old account's app-server
held its writer lock indefinitely. The new account's resume then failed
with 'This conversation is open in another Codex client'.

Detach now also unloads this thread's same-driver native threads that
were rebound away, taking the driver from the live runtime. Codex also
keeps the lock ~60s after unsubscribe until its idle sweep closes the
thread, so a resume refused for a thread this server released waits up
to 90s for thread/closed and retries once; threads held by genuinely
external clients still fail immediately.

Model: muse-wonder-1.0-contributor (Muse Code)
The usage panel could spend a banked reset credit, but the usage-limit
recovery banner/card only offered Resume at reset and Snooze until
reset. When the limited account reports banked reset credits, the banner
(web/desktop) and card (iOS/Android) now offer Reset usage, which
confirms then redeems a credit via the existing consumeResetCredit
command. Mobile's redemption hook moves to usage/useResetCredit.ts so
the panel and card share it.

Model: muse-wonder-1.0-contributor (Muse Code)
@changeset-bot

changeset-bot Bot commented Nov 8, 2026 •

Copy link
Copy Markdown

⚠️ No Changeset found

Latest commit: a6e9947

Merging this PR will not cause a version bump for any packages. If these changes should not result in a new version, you're good to go. If these changes should result in a version bump, you need to add a changeset.

Click here to learn what changesets are, and how to add one.

Click here if you're a maintainer who wants to add a changeset to this PR

Guard redemption before awaiting the RPC so confirmations already opened
cannot submit overlapping reset requests. Release the guard on success,
typed command failure or rejected transport, preserving execution-time
permission checks and explicit user confirmation.

Add focused hook regressions for duplicate approvals, each completion path,
subsequent attempts and permission revocation. The exact hook source passed
isolated controlled-promise checks with framework/transport stubs; the full
React/vite-plus suite and changed-file gate remain hosted validation.
Apply the mobile single-flight guard to the web/desktop usage hook while
preserving its reset/message result for the recovery banner. Clear pending
state after success, typed failure or transport rejection and recheck the
current management permission on each attempt.

Add a managed browser hook test for overlapping submissions, all completion
paths and retry after permission changes. Hash-verified source extraction
reproduced duplicate requests before and passed controlled-promise checks
after; framework and RPC boundaries were stubbed. TypeScript syntax and
whitespace checks passed. Actual React/browser execution, check:changed and
full CI remain required; repository dependencies are unavailable locally.
@ScriptedAlchemy
ScriptedAlchemy merged commit e9fffd3 into main Nov 9, 2026
16 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.

1 participant