Repository navigation
Conversation
…shared home A Claude config dir that symlinks `projects` into another home resumes that home's transcripts, but its continuation key was built from its own path, so T3 locked the model picker and rejected switching a thread between the two instances. Key Claude continuation by the home that owns `projects` (resolved through symlinks), which is unchanged for ordinary config dirs.
ApprovabilityVerdict: Approved at Macroscope's review found this PR approvable — The change is a narrowly scoped Claude continuation-key fix with one filesystem lookup and comprehensive symlink/fallback tests. The remaining modified file only updates registry expectations, with no schema, deployment, default, or static-analysis configuration impact. You can add or adjust custom eligibility rules. Learn more. |
|
Navigate logical layers of code changes, visualize relationships, and explore their blast radius. No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (3)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review. 📝 WalkthroughWalkthroughClaude continuation group keys now use the real path of the ChangesClaude continuation group keys
Priority: ➖ Normal Estimated code review effort: 2 (Simple) | ~10 minutes Change: Bug fix Merge Risk: ⚪ Minimal · up to This change lets Claude instances that share a transcripts directory switch within one thread. Ordinary config directories keep their existing behavior apart from the key prefix. No blocking merge risk is evident from the supplied evidence. Security Architecture ReviewSecurity architecture risk: 🔵 Low · up to Instances sharing transcript storage can now switch accounts without abandoning their native thread. The selected account’s configuration and credentials remain separate. No concrete security regression was established, but deployment-specific account-sharing policy and behavior during filesystem changes remain unverified. Retained concerns Security review detailsSecurity Blast Radius
Trust Boundaries and Controls
Resilience and Maintainability Implications
Hardening Proposals
🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
|
Note This comment is posted by Julius' dot Which checks were actually run on this head, and what were the results? The "How to check" section gives a test command and manual recipe, while the two images illustrate the design rather than an observed run. Please report the focused test result and whether a live switch between the symlinked homes was exercised, including platform and limits. That will establish the evidence required by the verification rule. Leaving this open for clarification. |
|
I ran it on the head and put the results in the description under Verification: the focused test (7/7), the related suites, and a live switch between two symlinked Claude homes on Linux with before and after screenshots. In short, the switch is now allowed and keeps context while the session is live. After a stop it still starts blank, which is #4766 and not changed by this PR. |
|
Also checked this together with #11908. Merging its head ( Live, same two-subscription setup: one turn on Claude, server restart so the session is stopped, then switch the thread to Claude Work. It answered with the code word set under Claude, and its Screenshots: https://dsbta0isuh6u.postplan.dev |
Dismissing prior approval to re-evaluate 0cd75a9
Keying on the parent of the resolved projects directory gave two homes the same key when their projects links pointed at sibling directories, so a switch between them chose native resume against a transcript store the target cannot read. The key is now the projects directory itself.
Dismissing prior approval to re-evaluate 6b563ef
|
Rechecked this on the new orchestrator. The context handoff does work for this switch on main, and this PR doesn't change it. What the PR covers is the narrower case where both Claude instances already share one transcript store and differ only in credentials: one It is the same rule Codex follows with On head
|
|
Windows data point, since the live runs in the description are on Linux Setup: Windows 10 with two Claude instances. The first uses the default Key derivation: With Node 24.21 outside T3, Impact on main: Switching a thread from one account to the other (target model Opus 5.5) goes through the context handoff. One switch on a long thread carried a summary plus 14 history items and omitted 944. Another carried a summary plus 12 items and omitted 90. The transcripts are reachable through both homes via the junction Not verified: I haven't run this branch live on Windows, so end-to-end resume evidence is still Linux only |
Problem
A common way to run two Claude subscriptions side by side is an auth overlay: a second
CLAUDE_CONFIG_DIRwith its own.credentials.json, whoseprojectsis a symlink to~/.claude/projects. Both accounts read and write the same transcripts, so either one can--resumethe other's session.T3 builds Claude continuation keys from the config dir path, so the two instances never match, and switching a thread between them (say, when one account hits its usage limit) goes through the context handoff. The handoff replays the thread as text under a 16k-token budget (
T3CODE_CONTEXT_HANDOFF_TOKEN_CAP) and drops items that do not fit. It carries messages and command output, but not other tool results: whatever the agent learned from files it read is gone, even though the transcript that holds it is on disk and readable by the target account.Change
makeClaudeContinuationGroupKeykeys an instance by its transcript store, the realpath of<config dir>/projects, asclaude:projects:<dir>. Whenprojectsis missing or a dangling symlink, it uses the unresolved<config dir>/projectspath, so homes that share nothing keep distinct keys.Equal keys make
decideProviderSessionTransitionpickrestart_and_resume. On that path the orchestrator already hands the native thread ref to the target instance ("Account overlays share native history" inOrchestrator.ts), including after the session has stopped, which is what #11908 fixed on the old orchestrator. Instances with separate stores still get the handoff.The first version keyed on the parent of the resolved
projects. CodeRabbit pointed out that two homes whoseprojectslink to sibling directories (/data/a,/data/b) would then share the key/data, and a switch would try to resume a transcript the target cannot read.6b563efkeys on the directory itself and adds that case to the tests.Risk: one
realpathwhen a Claude instance is built. The key format changes fromclaude:home:toclaude:projects:. Keys are computed at runtime and only compared for equality (the transition policy on the server, the model picker on the web), so nothing stored depends on the old format.Scope and approval
No prior issue. This is a small fix for the same defect class as #12616 (accepted and closed): a continuation key has to identify where the transcripts live, and here two instances with one transcript store get different keys. The change stays inside the function that derives the key. The transition policy, the native-resume path, and the handoff are untouched, and no setting, default, or UI changes. Codex already applies the same rule: with
shadowHomePath,auth.jsonstays private whilesessionsis shared, and the key follows the shared home. I keyed on the realpath instead of adding a Claude shadow-home setting, so existing overlay setups work without new configuration.Verification
vp test run apps/server/src/provider/Drivers/ClaudeHome.test.ts: 8/8. An overlay, a chained overlay, a symlinked home, and an overlay reached through an inheritedCLAUDE_CONFIG_DIRshare one key. A separate home, a home withoutprojects, a danglingprojectssymlink, and two homes linked to sibling directories each keep their own. Withprovider/Drivers,ProviderInstanceRegistryLive,ProviderSwitchServiceandProviderSessionTransitionPolicy: 147/147.apps/servertypecheck and lint on the changed files: clean.Live, in the web client against an isolated
vp run devserver (one dev state, a new thread per run). Linux 6.12 (Debian 13), Claude Code 2.1.288, Claude Sonnet 5. "Claude" uses~/.claude. "Claude Work" is a second config dir with its own credentials andprojects -> ~/.claude/projects, signed in to a different subscription.Claude reads a file holding a code word and replies only
DONE-READING. The file is deleted, the thread is switched to Claude Work, and Claude Work is asked for the code word without tools, so the word exists only in the Read tool result.2a45557query.openwithoutresume6b563efresume= the Claude session id,initreports the samesession_id6b563efEach screenshot is the conversation column of the thread. "1 changed file -1" is the test deleting the code-word file. Full-size versions: https://lj16g2p4ui4l.postplan.dev
main
2a45557: a "Context handoff" row before the question, then "I don't know". The timestamp line under it comes from that account's own CLAUDE.md.This PR
6b563ef, live session: no handoff row,PAPAYA-42.This PR
6b563ef, after a server restart stopped the session:PAPAYA-42.Not checked: macOS, Windows, the desktop and mobile clients, and a switch triggered by a real usage limit.