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
{{ message }}
Repository navigation
[Bug]: Switching compatible Codex accounts with shared CODEX_HOME uses portable handoff instead of native thread resume #16613
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
Use T3 Code nightly 0.0.46-nightly.20261005.2702.
Use Codex CLI 0.160.0.
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
Fully restart T3 Code after saving the provider settings.
Create a new T3 thread using Instance 1.
Complete several turns on Instance 1.
In the same T3 thread, switch the Codex account/provider instance to Instance 2.
Send another message.
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.
No relevant stack trace observed. The failure is behavioral: the target Codex instance reports receiving a T3 context handoff after the account switch.
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.
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.
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:
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.
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.
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.
Both are attached to the same Codex app-server process.
Conversation 2 remains attached, even if idle.
Switch Conversation 1 from A to B.
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.
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.
Activity
WISSAM492 commentedon Oct 6, 2026
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 writerfailure 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/resumeorthread/startis actually being used.juliusmarminge commentedon Oct 6, 2026
Note
Grok responding on behalf of Julius.
Confirmed on current
main: with two Codex instances sharing the sameCODEX_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
continuationKey(codex:home:<resolved path>) inCodexHomeLayout/codexContinuationIdentity.ProviderSessionTransitionPolicythen returnsrestart_and_resumewhen the instance changes but the continuation key matches.canResumeAcrossInstancesshould rebind the existing provider thread (keepnativeThreadRef), detach the old session, skipprepareProviderHandoff, and let Codexthread/resumecontinue the native conversation.Where it still falls into portable handoff
Two concrete gaps on
mainexplain falling through to a full T3 handoff instead:Queued start ignores an already-applied selection — In the queued-run starter,
canResumeAcrossInstancesonly runs whenthread.modelSelectionstill differs from the queued run's selection. Afterthread.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 livemessage.dispatchpath already documents this case ("Account overlays share native history… classify against the native thread's owner") and forces the plan current instance fromactiveProviderThread.providerInstanceId. The queued path never got that fix.Resume-failure fallback always materializes a full handoff — Even when Orchestrator does take the native-resume path,
ProviderTurnStartServicecatches a failedsession.resumeThread/thread/resume, starts a freshthread/start, and callsprepareProviderHandoffwithfull_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 forProvider resume failed; attempting a fresh native sessionand compare native Codex thread IDs before vs after — that would confirm this fallback vs the queued-plan gap.Related (not duplicates)
already has an active writer(you are not hitting this).No open fix PR found. Workaround of avoiding mid-thread instance switches is correct until this is fixed.
Labeling
bug+via-triage.WISSAM492 commentedon Oct 13, 2026
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.resumeThreadfails,ProviderTurnStartServicecan fall back to a fresh native thread and injectfull_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.
ProviderSessionManagerpreviously identified native threads to unload through persisted bindings, which may already have been rebound to the new provider instance.Additionally, Codex's
thread/unsubscribedoes 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/resumereturns the requested native thread ID.2. Reproduction and validation
The investigation used upstream commit:
98a6246d07624432a6e6d0ab4fb87bede90a49a6Environment:
Codex CLI
0.162.1Installed T3 nightly
0.0.46-nightly.20261012.3010Two 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 reproducibleReal protocol traces confirmed successful
thread/resumeoperations 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_writerfailure 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:
Conversation 1 and Conversation 2 use Account A.
Both are attached to the same Codex app-server process.
Conversation 2 remains attached, even if idle.
Switch Conversation 1 from A to B.
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.