Skip to content

fix(claude-accounts): relaunch Claude sessions in the folder their transcript is filed under - #32033

Closed
asunghong wants to merge 2 commits into
stablyai:mainfrom
asunghong:fix/claude-resume-session-folder
Closed

asunghong wants to merge 2 commits into
stablyai:mainfrom
asunghong:fix/claude-resume-session-folder

Conversation

@asunghong

Copy link
Copy Markdown
Contributor

ELI5

When Orca restarts a Claude terminal (after you switch Claude accounts, or when Orca's terminal service comes back after a restart or update), it types claude --resume <session-id> into a fresh shell. That shell could start in a sub-folder (the last folder the terminal reported, or the tab's start folder) instead of the session's project, so Claude treated the sub-folder as a new project: it asked "trust this folder?" and, once accepted, ran without the project's CLAUDE.md / AGENTS.md and hooks. Now Orca starts the relaunch in the folder Claude filed the session under.

What Changed

Before

  • Account-switch restart: the new shell starts in the tab's start folder (tab.startupCwd ?? worktree path), whatever folder the session belongs to.
  • Cold restore (the terminal daemon is gone): the shell is revived in the checkpoint's last OSC 7 folder, which can be a sub-folder of the project, and the resume line runs there.
  • Either way claude --resume can land in <project-dir>/<sub-folder>: trust prompt, project rules and hooks missing.

After

  • Both paths start the relaunch in the folder whose Claude encoding names the transcript's bucket (<CLAUDE_CONFIG_DIR>/projects/<encoded launch folder>/<session-id>.jsonl).

Mechanism

  • src/shared/claude-project-dir-encoding.ts (moved unchanged from src/main/ai-vault/ so the renderer can use it) gains findClaudeTranscriptProjectFolder(transcriptPath, candidates): it checks each candidate and its parent folders and returns the first one that encodes to the bucket name, or null.
  • Account switch (renderer): for host-native local panes, resolveAgentAccountRestartResumeStartup checks the pane's last reported folder (mounted panes), the tab's start folder and the worktree path, and returns the match as cwd. The mounted (use-terminal-pane-process-exit-actions.ts) and detached (codex-detached-pane-restart.ts) restart executors spawn there.
  • Cold restore (main): a local Claude resume spawn carries a new optional PtySpawnOptions.claudeResumeTranscriptPath (spawn-options-assembly.ts). The daemon adapter's cold-restore folder choice (new src/main/daemon/cold-restore-startup-cwd.ts) prefers the matching folder among the requested and recovered folders, then keeps the existing "recovered folder unless it was deleted" rule.
  • When nothing matches (unknown transcript layout, a shortened bucket name, sessions from another host), and for WSL, SSH and remote panes, behaviour is unchanged.

Why

Claude Code resolves --resume against the project bucket of its cwd and loads project config from the cwd. Codex resumes into the cwd recorded in its rollout, which is why only Claude panes showed this. The pane's provider session already carries the hook-reported transcriptPath, so its bucket name identifies the right folder with no file IO.

Alternatives considered:

  • Always prefer the requested folder over the recovered one for agent resumes: simpler, but breaks sessions that were started in a sub-folder (cd <sub-folder> && claude).
  • Read the cwd field from the transcript JSONL: adds file IO to the restart path, and that field records the working folder per message, which can change during the session.
  • Prefix cd <dir> && to the resume line: needs per-shell quoting and leaves the user's shell in a different folder after the agent exits.

Linked Issue

Part of #31545

Scope: this PR fixes the wrong-folder relaunch (item 1 of the issue) on the account-switch restart and the daemon cold-restore paths. Other --resume paths that start a fresh shell in the tab's folder (hibernation wake, a fresh cold-restore resume when the daemon has no history) are unchanged, and WSL panes keep today's folder (their transcript and OSC 7 paths are guest paths; matching them against UNC worktree paths is a follow-up). The lost effort level (item 2), duplicate processes for one session id (item 3) and showing which sessions moved (item 4) are left for follow-ups. Launch arguments Orca recorded for the pane (for example --model) are already replayed on resume; an effort changed inside the session with /effort is saved in the account profile's own settings, which Orca does not observe, so carrying it needs a separate design.

Visual Proof

N/A: no UI change. The change is which folder a relaunched Claude session starts in; it is covered by the unit tests below. A before/after recording was not captured.

Testing

  • I manually tested these changes locally
  • Automated tests added/updated, or explained why not below

New or extended tests:

  • src/shared/claude-project-dir-encoding.test.ts: walking up from a sub-folder, a session started in a sub-folder, falling through to a later candidate, Windows separators and drive root, a UNC walk that stops at the share, and no-match cases.
  • src/renderer/src/lib/agent-account-restart-startup.test.ts: a Claude restart resolves the session's folder from the pane's sub-folder; no match leaves the folder to the tab; a WSL pane's guest folder is never used.
  • src/renderer/src/components/terminal-pane/codex-detached-pane-restart.test.ts: an unmounted Claude pane whose tab starts in a sub-folder respawns in the session's folder.
  • src/renderer/src/components/terminal-pane/use-terminal-pane-process-exit-actions.test.ts: a mounted pane hands its last folder to the resolver and spawns in the resolved folder.
  • src/main/ipc/pty/ipc/spawn-options-claude-resume.test.ts: only a local Claude resume hands the transcript path to the provider (not SSH, not Codex).
  • src/main/daemon/daemon-pty-adapter-history-checkpoints.test.ts: a cold-restored Claude resume revives in the transcript's folder, not the recovered sub-folder.

Run locally (macOS, Node, Vitest on Node):

  • vitest run --config config/vitest.config.ts on the new and changed tests plus every suite importing a changed module (account switch, restart readiness, spawn options, ai-vault bucket layouts, worktree retirement): all passed. Each new test fails with its production change reverted.
  • Broader regression run over src/renderer/src/components/terminal-pane, src/main/ipc/pty, src/main/ai-vault* and the Claude account libraries (910 files): no new failures. The few failures seen there also occur on the base commit in the same environment (daemon tests that need a Unix socket under /tmp, two suites that fail on base, and one timing-sensitive suite that is flaky on base).
  • node config/scripts/run-typecheck-projects-in-parallel.mjs --compiler=native for the node, web and cli projects: passed.
  • node config/scripts/check-changed-code-quality.mjs, the max-lines, path-literal, main-sync-spawn, compat-boundary and process-host ratchets, oxlint (default and anti-slop configs) and oxfmt --check on changed files: passed.
  • Not run locally: the daemon adapter integration test, because the sandbox blocked its Unix socket under /tmp; CI covers it. Linux, Windows and SSH were not tested by hand.

AI Disclosure

Investigation, fix and tests were written with Claude Code (Claude Opus 5.5) and checked by a separate AI review pass. The author reported the bug (#31545) and has not yet tested this build by hand.

Review

AI review summary:

  • Cross-platform: Windows drive paths keep their separators and drive root; a UNC walk stops at the share; WSL panes are excluded so a guest POSIX folder never becomes a Windows host spawn folder.
  • SSH/remote: the provider option is set only when there is no connectionId; Claude account-switch restarts are local-only already.
  • Agents: Claude only; Codex and other agents take the old paths unchanged.
  • Performance: pure string comparison over a few candidates times the path depth; no file IO is added.
  • Security: the transcript path is only compared as a string, never opened, and is already sanitised by normalizeAgentProviderSession.
  • Compatibility: the new optional provider option is read only by the desktop's daemon adapter in main; it is not sent over the daemon wire protocol or to SSH relays, so mixed versions are unaffected.
  • Folder workspaces: the worktree path candidate is the folder workspace's root, so they are covered the same way.

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

Ensure no issues in: Security, Cross-platform support (Linux, Windows, Mac), Remote SSH, Mobile, general backwards compatibility, performance

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason (N/A: no UI change, see Visual Proof)
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

🤖 Generated with Claude Code

…anscript is filed under

An account-switch restart started the new shell in the tab's start folder, and
a cold restore revived the shell in the last folder the terminal reported
(OSC 7), which can be a sub-folder rather than the session's project.
`claude --resume` takes its project from the process cwd, so the session came
back in the wrong folder: Claude asked to trust it and ran without the
project's CLAUDE.md and hooks. Codex was unaffected because `codex resume`
returns to the folder its rollout records.

Both relaunch paths now pick the folder whose Claude encoding names the
transcript's `projects/<bucket>`. The account-switch restart checks the pane's
last reported folder (mounted panes), the tab's start folder and the worktree,
each with its parents, for host-native local panes only. The daemon's cold
restore checks the requested and recovered folders for local Claude resumes.
When nothing matches, the folder is chosen as before.

Moves the Claude project-dir encoding to src/shared so the renderer can use it.

Fixes stablyai#31545

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b3c07461-8e20-4b0f-a20b-dcf0f626eae6
📥 Commits

Reviewing files that changed from the base of the PR and between 1bca82f and dc4b7ca.

📒 Files selected for processing: 2
  • src/shared/claude-project-dir-encoding.test.ts
  • src/shared/claude-project-dir-encoding.ts
🚧 Files skipped from review as they are similar to previous changes: 1
  • src/shared/claude-project-dir-encoding.test.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

📝 Walkthrough

The change moves Claude project-path encoding helpers into a shared module and adds transcript-based project-folder lookup. PTY startup options now carry the transcript path for local Claude resumes. Cold restores and eligible local host-native account restarts use the lookup to select a working directory. Tests cover path matching, cold restores, and restart cases, including unmatched paths and WSL.

Priority: ➖ Normal

Merge Risk: ⚪ Minimal · up to dc4b7

The previously identified ambiguous-folder selection is addressed in the described change. No actionable merge-blocking issue remains in the supplied evidence; proceed with normal checks.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 75.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 12 functions across 16 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title names the main change: Claude sessions are relaunched in the folder their transcript is filed under. The changeset matches this. It adds transcript-bucket folder resolution for account-switc…
Description check Passed The description follows the repository template closely. It has ELI5, What Changed (before/after and mechanism), Why (with alternatives considered), Linked Issue (Part of #31545, with explicit scope),…
Linked Issues check Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check Passed Check skipped because no linked issues were found for this pull request.
Full details: Title check

Explanation

The title names the main change: Claude sessions are relaunched in the folder their transcript is filed under. The changeset matches this. It adds transcript-bucket folder resolution for account-switch restarts and daemon cold restores, and it is scoped to local Claude panes. The title uses the fix(claude-accounts) convention and stays under a single sentence. Its length (92 characters) is acceptable.

Full details: Description check

Explanation

The description follows the repository template closely. It has ELI5, What Changed (before/after and mechanism), Why (with alternatives considered), Linked Issue (Part of #31545, with explicit scope), Visual Proof (N/A with reason), Testing (automated tests listed and local runs described), AI Disclosure, Review, Agent skill upstream boundary, Notes, and Checklist. Two points are weak. The Linked Issue section uses 'Part of #31545' instead of the template's 'Fixes #' form, and the template asks for an issue to be linked. The manual-testing box is unchecked, and the author states the build has not been tested by hand. The pnpm lint/typecheck/test/build checklist item is also unchecked. These gaps are explained in the text, so the description is mostly complete.

  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1


ℹ️ Review info
⚙️ Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 6beacb45-c467-40b2-b84a-c6772b3a3d27
📥 Commits

Reviewing files that changed from the base of the PR and between d053ff9 and 1bca82f.

📒 Files selected for processing: 17
  • src/main/ai-vault/claude-project-dir-encoding.ts
  • src/main/ai-vault/session-cwd-bucket-layouts.ts
  • src/main/daemon/cold-restore-startup-cwd.ts
  • src/main/daemon/daemon-pty-adapter-history-checkpoints.test.ts
  • src/main/daemon/daemon-pty-session-spawn.ts
  • src/main/ipc/pty/ipc/spawn-options-claude-resume.test.ts
  • src/main/ipc/pty/spawn-options-assembly.ts
  • src/main/providers/pty-provider-contract.ts
  • src/main/worktree-retirement-discovery.ts
  • src/renderer/src/components/terminal-pane/codex-detached-pane-restart.test.ts
  • src/renderer/src/components/terminal-pane/codex-detached-pane-restart.ts
  • src/renderer/src/components/terminal-pane/use-terminal-pane-process-exit-actions.test.ts
  • src/renderer/src/components/terminal-pane/use-terminal-pane-process-exit-actions.ts
  • src/renderer/src/lib/agent-account-restart-startup.test.ts
  • src/renderer/src/lib/agent-account-restart-startup.ts
  • src/shared/claude-project-dir-encoding.test.ts
  • src/shared/claude-project-dir-encoding.ts
💤 Files with no reviewable changes: 1
  • src/main/ai-vault/claude-project-dir-encoding.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 9 remain after this review.

…et fits two folders

/a/foo-bar and /a/foo/bar both encode to -a-foo-bar, so a bucket name alone
cannot choose between them. findClaudeTranscriptProjectFolder now collects the
distinct matching folders and returns null unless exactly one matches, so the
cold-restore and account-switch callers keep their existing folder choice.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@nwparker

Copy link
Copy Markdown
Contributor

Thanks @asunghong — your diagnosis drove this. #33527 now records each session's start folder from Claude's hooks, and #33659 (merged, with your co-author credit) makes the account-switch restart use it, so the transcript-bucket matching isn't needed. Closing in favour of those.

nwparker added a commit that referenced this pull request Oct 25, 2026
… it was started in (#33659)

Claude account-switch restarts now pass the folder the restarted shell opens in (tab start folder, else workspace), so #33527's `cd <start folder> &&` resume prefix applies and a session started in a subfolder resumes there. Codex account restarts benefit the same way.

Refs #31545
Supersedes #32033

Co-authored-by: ASH <7201776+asunghong@users.noreply.github.com>
@nwparker nwparker closed this Oct 25, 2026
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.

2 participants