Skip to content

[Bug]: Switching compatible Codex accounts with shared CODEX_HOME uses portable handoff instead of native thread resume #16613

Open

Description

@WISSAM492

Before submitting

  • I searched existing issues and did not find a duplicate.
  • I included enough detail to reproduce or investigate the problem.

Area

apps/server

Steps to reproduce

  1. Use T3 Code nightly 0.0.46-nightly.20261005.2702.

  2. Use Codex CLI 0.160.0.

  3. Configure two authenticated Codex provider instances using the documented shared-home setup:

    Instance 1:

    • CODEX_HOME: ~/.codex
    • Shadow home: empty

    Instance 2:

    • CODEX_HOME: ~/.codex
    • Shadow home: ~/.codex_2nd
  4. Fully restart T3 Code after saving the provider settings.

  5. Create a new T3 thread using Instance 1.

  6. Complete several turns on Instance 1.

  7. In the same T3 thread, switch the Codex account/provider instance to Instance 2.

  8. Send another message.

  9. Ask Instance 2, without using t3_thread_read, whether it received the original conversation natively and what history it received.

This reproduces consistently for me.

Expected behavior

Because both Codex instances share the same CODEX_HOME, switching between them should resume the same native Codex thread/session.

The target account should continue using native Codex thread resume, preserving the full provider-native conversation state.

A compatible Codex account switch should not silently fall back to a portable/context handoff.

Actual behavior

After switching from Instance 1 to Instance 2, the target Codex instance receives a T3 context handoff instead of appearing to resume the same native Codex thread.

On a fresh short test thread, the target explicitly reported:

"This chat started with a context handoff containing 13 intact items and 0 omitted items..."

On a longer thread, the target reported that only a selected subset of history was included and that additional items were omitted.

The T3 UI still shows one continuous conversation, but the target Codex instance is receiving handoff context rather than full native continuation.

Impact

Major degradation or frequent failure

Version or commit

T3 Code: 0.0.46-nightly.20261005.2702

Environment

  • OS: Ubuntu Linux - T3 Code desktop app - T3 Code: 0.0.46-nightly.20261005.2702 - Codex CLI: 0.160.0 - Provider: Codex - Two separately authenticated ChatGPT/Codex accounts Provider configuration: Instance 1: - CODEX_HOME: ~/.codex - Shadow home: empty Instance 2: - CODEX_HOME: ~/.codex - Shadow home: ~/.codex_2nd

Logs or stack traces

No relevant stack trace observed. The failure is behavioral: the target Codex instance reports receiving a T3 context handoff after the account switch.

Screenshots, recordings, or supporting files

Screenshot 2026-10-07 014223.png
Screenshot 2026-10-07 014309.png
Screenshot 2026-10-07 014444.png
Screenshot 2026-10-07 014401.png

Workaround

Avoid switching Codex provider instances within an important long-running thread.

Staying on the same Codex instance preserves the current native session behavior. Using the account switch currently risks falling back to a context handoff.

Activity

  1. added
    bugSomething is broken or behaving incorrectly.
    needs-triageIssue needs maintainer review and initial categorization.
    on Oct 6, 2026
  2. WISSAM492 commented on Oct 6, 2026

    @WISSAM492
    Author

    Additional context after reproducing this again:

    The current Codex docs explicitly state that compatible accounts can continue the same thread, and that a shared CODEX_HOME + shadow home setup shares Codex sessions/configuration.

    This also appears related to #4766, which documented loss of the provider-native resume cursor when switching between compatible provider instances and was later closed by Orchestrator V2 / #2829.

    The important difference from #8259 is that I am NOT seeing an already has an active writer failure here. The V2 switch succeeds, but the target Codex instance appears to receive a T3 context handoff instead of native continuation.

    If useful, I can collect provider/session logs or native Codex thread IDs before and after the switch to verify whether thread/resume or thread/start is actually being used.

  3. juliusmarminge commented on Oct 6, 2026

    @juliusmarminge
    Member

    Note

    Grok responding on behalf of Julius.

    Confirmed on current main: with two Codex instances sharing the same CODEX_HOME (auth-overlay / shadow home on one of them), a mid-thread account switch is supposed to resume the same native Codex thread, not inject a T3 portable context handoff. Your setup matches the documented multiple CLI logins table, and the symptom ("context handoff containing N intact items") is a real portable-handoff path.

    Intended path

    • Shared home → same continuationKey (codex:home:<resolved path>) in CodexHomeLayout / codexContinuationIdentity.
    • ProviderSessionTransitionPolicy then returns restart_and_resume when the instance changes but the continuation key matches.
    • Orchestrator canResumeAcrossInstances should rebind the existing provider thread (keep nativeThreadRef), detach the old session, skip prepareProviderHandoff, and let Codex thread/resume continue the native conversation.

    Where it still falls into portable handoff

    Two concrete gaps on main explain falling through to a full T3 handoff instead:

    1. Queued start ignores an already-applied selection — In the queued-run starter, canResumeAcrossInstances only runs when thread.modelSelection still differs from the queued run's selection. After thread.model-selection.set / provider.switch, those already match, so the switch plan is skipped and a portable handoff is prepared for the prior instance's runs. The live message.dispatch path already documents this case ("Account overlays share native history… classify against the native thread's owner") and forces the plan current instance from activeProviderThread.providerInstanceId. The queued path never got that fix.

    2. Resume-failure fallback always materializes a full handoff — Even when Orchestrator does take the native-resume path, ProviderTurnStartService catches a failed session.resumeThread / thread/resume, starts a fresh thread/start, and calls prepareProviderHandoff with full_thread_summary (provider_resume_fallback). That produces exactly the "intact / omitted items" preamble you saw. If you can grab server logs around the switch, look for Provider resume failed; attempting a fresh native session and compare native Codex thread IDs before vs after — that would confirm this fallback vs the queued-plan gap.

    Related (not duplicates)

    No open fix PR found. Workaround of avoiding mid-thread instance switches is correct until this is fixed.

    Labeling bug + via-triage.

  4. added
    via-triageFiled through npx t3 triage
    and removed
    needs-triageIssue needs maintainer review and initial categorization.
    on Oct 6, 2026
  5. WISSAM492 commented on Oct 13, 2026

    @WISSAM492
    Author

    Update — Reproduced on upstream, root causes identified, and partial fix validated (Oct 13, 2026)

    I conducted a deeper investigation of this issue against current upstream T3 Code, including automated regression tests and real Codex app-server experiments with two authenticated accounts.

    The original issue is now reproducible at the orchestration/protocol level. It is not limited to model self-reported context loss, my original Ubuntu installation, or CLI shadow-home authentication.

    1. Confirmed failure paths

    The investigation identified several related problems:

    A. Queued account switches can bypass native-continuation classification.

    When a provider selection has already been persisted, the queued-run starter may skip the provider-switch plan because the queued selection matches thread.modelSelection, even though the active native thread belongs to a different provider instance.

    This can incorrectly trigger a portable context handoff instead of restart_and_resume.

    Location: Orchestrator.ts, queued-run dispatch.

    B. Failed native resume silently replaces the native conversation.

    When session.resumeThread fails, ProviderTurnStartService can fall back to a fresh native thread and inject full_thread_summary.

    This explains how a seemingly continuous T3 conversation can actually lose its original provider-native continuity.

    C. Native writer ownership can survive account switching.

    ProviderSessionManager previously identified native threads to unload through persisted bindings, which may already have been rebound to the new provider instance.

    Additionally, Codex's thread/unsubscribe does not immediately release the original native writer.

    If the source Codex process is shared by another attached conversation, T3 cannot safely terminate that process to free the writer.

    This is a separate remaining blocker to reliable account switching.

    D. Additional lifecycle issues

    The investigation also identified premature active-run detachment during selection changes, non-proactive sharing of the native writer-lock directory, and missing validation that thread/resume returns the requested native thread ID.

    2. Reproduction and validation

    The investigation used upstream commit:

    98a6246d07624432a6e6d0ab4fb87bede90a49a6

    Environment:

    • Codex CLI 0.162.1

    • Installed T3 nightly 0.0.46-nightly.20261012.3010

    • Two distinct managed ChatGPT OAuth accounts

    • Isolated T3 configuration, native homes, and disposable conversations

    Results:

    Verification | Result -- | -- Selected regression cases on unmodified upstream | 9 failed, 2 passing controls Same 11 cases after the patch | 11 passed Total focused tests after patch | 430 passed Server typecheck | Passed Real managed OAuth model turns | 53 completed Immediate A → B → A switching cycles | 3 successful Queued B → A → B switching cycles | 3 successful Portable handoffs during those six cycles | 0 Native thread identity during those cycles | Preserved Long-thread restart recovery | Successful Shared-process writer conflict | Still reproducible

    Real protocol traces confirmed successful thread/resume operations returning the original native thread ID.

    The unmodified-upstream regression failures and the patched results were tested separately.

    3. Managed OAuth does not inherently fix this

    Both managed OAuth accounts authenticated successfully and completed actual model turns.

    However, a native active_writer failure was reproduced after authentication.

    Managed OAuth changes credential management but uses the same native-continuation and orchestration infrastructure.

    Therefore, migrating from CLI shadow-home accounts to managed OAuth alone is not a reliable fix for this issue.

    4. Current patch status

    A local, uncommitted patch now:

    • Correctly classifies queued switches against the active native thread owner.

    • Preserves active-run bindings until the run settles.

    • Retains the saved native thread reference when Codex resume fails.

    • Prevents silent replacement with a full-history handoff on Codex resume failure.

    • Tracks and releases the correct native writer when safe.

    • Validates native resume identity.

    • Proactively shares the writer-lock namespace across compatible homes.

    The patch modifies five production files and five test files.

    This is a partial fix, not a claim of complete resolution.

    5. Remaining blocker: shared Codex app-server processes

    The unresolved case is reproducible:

    1. Conversation 1 and Conversation 2 use Account A.

    2. Both are attached to the same Codex app-server process.

    3. Conversation 2 remains attached, even if idle.

    4. Switch Conversation 1 from A to B.

    5. Codex rejects native resume because the source process still retains the original writer.

    After the patch, T3 preserves the native thread ID and reports the failure explicitly rather than silently starting a replacement conversation.

    A real disposable-server restart subsequently allowed Account B to resume the original 20-turn native conversation.

    The installed Codex protocol does not expose a verified immediate, non-destructive per-thread writer-release operation. A complete fix may therefore require safer process ownership/isolation in T3 or additional upstream protocol support.

    6. Next steps

    I am separately investigating process isolation/pooling alternatives to resolve the remaining writer conflict without interrupting unrelated conversations.

    The validated corrections appear suitable for upstream review independently of that larger architectural change.

    I can provide the focused patch, regression tests, engineering report, and redacted protocol evidence for review.

    I recommend keeping #16613 open until compatible account switching reliably preserves native continuity, including when multiple conversations share an app-server process.

    Related: #8259, #11136, #4766.

    Note: CLI shadow-authentication E2E and mixed CLI/managed switching were not fully validated inside T3. Live managed OAuth switching and independent native CLI protocol resumes were verified.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething is broken or behaving incorrectly.via-triageFiled through npx t3 triage

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions