Skip to content

Fix Claude fork/resume failing when session changed directories - #5154

Merged
lawrencecchen merged 4 commits into
mainfrom
feat-claude-fork-cwd-drift
Jun 2, 2026
Merged

lawrencecchen merged 4 commits into
mainfrom
feat-claude-fork-cwd-drift

Conversation

@lawrencecchen

@lawrencecchen lawrencecchen commented Jun 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Forking (and restoring) a Claude conversation could fail with No conversation found with session ID: ... when the session had moved directories since it started.

Claude stores each transcript under the project directory derived from the cwd the session was created in, and claude --resume / --fork-session only locate it from that same directory. cmux's Claude hook reports the agent's current cwd, which drifts when the agent cds elsewhere mid-session (a very common pattern: start in a repo root, then move into a worktree for the rest of the session). The restore snapshot took its working directory from that drifted cwd, so fork/resume cd into the wrong directory and Claude can't find the transcript there.

Observed concretely: session a1fcdb44 started in /Users/lawrence/fun/cmuxterm-hq, so its transcript lives at projects/-Users-lawrence-fun-cmuxterm-hq/a1fcdb44-….jsonl. 27 seconds in it cd'd into worktrees/feat-ios-swift-mobile-core and worked there for ~2 days. A fork launched into the worktree failed; a fork launched from the start directory succeeded. Same session, only the directory differed.

Fix: for Claude, resolve the restore snapshot's working directory to the candidate directory whose project folder actually holds the transcript (prefer the launch cwd, matched first against the transcript's known storage path and then against the config directory on disk), before falling back to the recorded cwd. No transcript is copied; cmux just cds into where it already lives. Also fixes encodeClaudeProjectDir to replace . with -, matching Claude's real project-directory naming (e.g. /x/.claude -> -x--claude), which the match relies on.

This restores consistency: resume already used the launch directory and worked; fork now does the same.

Testing

  • Added RestorableAgentSessionIndexTests.testClaudeForkResolvesDriftedCwdViaTranscriptPath and ...ViaConfigScanWhenTranscriptPathMissing. Two-commit red/green: the first commit adds the tests (red), the second adds the fix (green). They build a hook store whose recorded cwd is a drifted sibling directory while the transcript lives under the launch cwd, then assert the restored snapshot's working directory and forkCommand cd into the launch cwd, not the drift.
  • Unit tests run in CI (cmux-unit / tests job); local test runs are disabled in this environment.

Issues

  • Reported via internal dogfood (no GitHub issue).

Scope and known follow-up

This PR fixes the reported symptom: forking a Claude conversation (a snapshot-driven action) that had changed directories. Verified empirically: the same session forks successfully from its start directory but failed from the drifted directory.

The same root-cause drift also reaches the auto-resume binding path (source: agent-hook, --resume), whose cwd originates in the CLI hook (CLI/cmux.swift) recording Claude's reported cwd and is re-applied at restore via replacingRequiredChangeDirectoryPrefix using the binding's own cwd, bypassing the snapshot fixed here. A persisted drifted --resume binding can therefore still fail auto-resume. Fixing that cleanly spans a second target (the CLI hook) with broader blast radius on the shared auto-resume path, so it is intentionally left as a focused follow-up rather than rushed into this PR.


Note

Medium Risk
Changes how Claude restore snapshots choose working directories and unifies project-dir encoding used for transcript lookup and session-index cwd filters; behavior is well covered by new tests but affects fork/resume shell commands.

Overview
Fixes Claude fork and restore when the agent’s hook-reported cwd has drifted after mid-session cd (e.g. repo root → worktree). Restore snapshots now set workingDirectory via restorableWorkingDirectory: for Claude only, pick launch vs recorded cwd by matching the transcript’s on-disk project folder (from transcriptPath when present, else scan CLAUDE_CONFIG_DIR), so generated forkCommand / resume shells cd where Claude actually stores the session.

Also corrects encodeClaudeProjectDir to map . → - like Claude’s project dirs, and single-sources that helper in SessionIndexStore for cwd-scoped session listing so dotted paths stay consistent with discovery.

Adds regression tests for drifted-cwd fork resolution (with and without transcriptPath).

Reviewed by Cursor Bugbot for commit d4c5021. Bugbot is set up for automated code reviews on this repo. Configure here.

Summary by CodeRabbit

  • Bug Fixes

    • Improved session restoration to correctly resolve working directories when they differ from where transcripts are stored. Sessions now reliably resume in the proper project directory, even when paths have changed.
  • Tests

    • Added test coverage for session restoration scenarios with drifted working directories.

lawrencecchen and others added 2 commits June 1, 2026 17:23
A Claude session can start in one directory and cd into another (repo root
then a worktree). Claude keeps the transcript in the start directory's project
folder, but the hook-reported cwd drifts to the later directory. cmux builds the
restore snapshot's working directory from that drifted cwd, so fork/resume cd
into the wrong directory and claude --resume fails with "No conversation found".

These tests assert the restored snapshot resolves to the directory that actually
holds the transcript. They fail against current behavior.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Restore snapshots took their working directory from the hook-reported cwd, which
drifts when the agent cds away from where it started. Claude --resume and
--fork-session only find a session from the directory it was created in, so a
drifted cwd makes them fail with "No conversation found" even though resuming
from the start directory works.

For Claude, resolve the snapshot working directory to the candidate directory
whose project folder actually holds the transcript: prefer the launch cwd,
matched first against the transcript's known storage path and then against the
config directory on disk, before falling back to the recorded cwd. Also fix
encodeClaudeProjectDir to replace "." with "-" (matching Claude's real project
directory naming, e.g. "/x/.claude" -> "-x--claude"), which the match relies on.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@vercel

vercel Bot commented Jun 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 2, 2026 2:08am
cmux-staging Building Building Preview, Comment Jun 2, 2026 2:08am

@coderabbitai

coderabbitai Bot commented Jun 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: dca88cf5-021a-4b67-ab24-b5a6e5b261ea

📥 Commits

Reviewing files that changed from the base of the PR and between bd1e119 and d4c5021.

📒 Files selected for processing (2)
  • Sources/SessionIndexStore.swift
  • cmuxTests/RestorableAgentSessionIndexTests.swift

📝 Walkthrough

Walkthrough

Session restore now computes snapshot workingDirectory via a new helper that prefers the transcript’s project directory for Claude sessions (matching encoded project-dir or probing config roots). The Claude project-dir encoder now replaces both '/' and '.' with '-'. Tests cover drifted-cwd scenarios with and without explicit transcriptPath.

Changes

Claude Session Working Directory Resolution

Layer / File(s) Summary
Claude project directory encoding fix
Sources/RestorableAgentSession.swift
encodeClaudeProjectDir(_:) replaces both / and . with -, fixing dotted project path encoding.
Working directory resolution with transcript validation
Sources/RestorableAgentSession.swift
restorableWorkingDirectory(for:kind:fileManager:lookup:) validates Claude working directory candidates by matching project directory names from transcript paths or probing config roots for transcript existence.
Integration into session snapshot creation
Sources/RestorableAgentSession.swift, Sources/SessionIndexStore.swift
Session snapshot loading now derives workingDirectory via restorableWorkingDirectory instead of direct cwd normalization; SessionIndexStore fast-path uses the shared encodeClaudeProjectDir.
Test coverage for drifted working directory scenarios
cmuxTests/RestorableAgentSessionIndexTests.swift
Two tests validate drifted cwd resolution with explicit transcript paths and via config scanning. Helpers driftedHookRecord and expectedClaudeProjectDirName support fixtures.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

Poem

🐰 I hopped through transcripts old and new,

found dotted paths and changed their view,
When CWDs have drifted far,
the transcript points the star,
and home is found with a hop and a chew.


Important

Pre-merge checks failed

Please resolve all errors before merging. Addressing warnings is optional.

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 26.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
Cmux Swift @Concurrent ❓ Inconclusive No result was produced after verification. Marking as INCONCLUSIVE. Re-run the check or adjust instructions to produce a final result.
✅ Passed checks (16 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately and concisely summarizes the main change: fixing Claude fork/resume when session directories have changed, which is the core issue addressed throughout the changeset.
Description check ✅ Passed The PR description covers all required template sections: a clear Summary explaining what changed and why, comprehensive Testing details with specific test names and approach, and a complete Checklist. The description is thorough and addresses the root cause well.
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.
Cmux Swift Actor Isolation ✅ Passed Production changes add static helpers and refactor encoder logic. RestorableAgentSessionIndex is Sendable; mutable cache is internal and temporary, never exposed, so no Swift 6 violations.
Cmux Swift Blocking Runtime ✅ Passed No blocking/timing synchronization patterns introduced in production code. New logic uses synchronous file I/O and caching only.
Cmux No Hacky Sleeps ✅ Passed All PR changes are Swift files; check applies only to TypeScript, JavaScript, shell, and non-Swift build scripts. Swift timing covered by separate check.
Cmux Algorithmic Complexity ✅ Passed PR uses only fixed-size collections (max 2 candidates, ~4 config roots) with early-return nested loops; no scalable collection scans; startup-path function; test-only additions allowed.
Cmux Swift Concurrency ✅ Passed New synchronous functions introduced for Claude session directory resolution; no legacy async patterns detected—no DispatchQueue, Combine, completion handlers, or fire-and-forget Tasks.
Cmux Swift File And Package Boundaries ✅ Passed +67 lines to existing file, below 250-line threshold for oversized files. PR removes duplicate code and consolidates to single source.
Cmux Swift Logging ✅ Passed No logging violations found. Production code lacks print/debugPrint/dump/NSLog. SessionIndexStore uses nonisolated private Logger. Tests properly exempt.
Cmux User-Facing Error Privacy ✅ Passed PR adds only private helper functions and tests. No new user-facing error messages, vendor names, or sensitive data exposed to users.
Cmux Full Internationalization ✅ Passed No user-facing text, error messages, UI strings, or localization catalog entries added. Changes limited to private functions, test code (exempt), and developer documentation comments.
Cmux Swiftui State Layout ✅ Passed PR modifies only non-SwiftUI helper functions; introduces no new @Published, @State, @Observable, ObservableObject, GeometryReader, or lazy list store references.
Cmux Architecture Rethink ✅ Passed PR is a small correctness fix with clear owner (RestorableAgentSessionIndex), explicit invariant (transcript path determines project directory), and zero timing/locking/observer antipatterns.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR introduces no NSWindow, NSPanel, NSWindowController, or SwiftUI Window code. All changes are internal session restoration logic and unit tests.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat-claude-fork-cwd-drift

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 and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 2, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes Claude fork/resume failing with "No conversation found" when an agent session drifts its working directory mid-session. The hook-reported cwd no longer blindly drives the cd for fork/resume; instead, cmux resolves the snapshot's workingDirectory by finding which candidate (launch cwd or recorded cwd) actually owns the Claude transcript on disk.

  • restorableWorkingDirectory (new): matches candidates against the transcript storage path via encodeClaudeProjectDir, falls back to a config-directory probe, then falls back to the recorded cwd.
  • encodeClaudeProjectDir fix: now replaces . as well as / with -, matching Claude's real on-disk project directory naming; the duplicate in SessionIndexStore.swift is removed.
  • Tests: two end-to-end fixtures assert that snapshot.workingDirectory and forkCommand resolve to the launch cwd, not the drifted directory.

Confidence Score: 5/5

Safe to merge; scoped to Claude snapshot restoration with a fallback chain that preserves pre-PR behavior when no transcript match is found.

The restore path has a reliable fallback to the recorded cwd whenever transcript-path matching or config-scan probing produces no match, so the worst case for any uncovered edge is unchanged behavior rather than a new failure. No actor-isolation or concurrency concerns are introduced.

The decodeClaudeProjectDir comment in SessionIndexStore.swift is now inconsistent with the updated encoding and should be updated.

Reviews (3): Last reviewed commit: "Single-source Claude project-dir encodin..." | Re-trigger Greptile

Comment on lines 1344 to 1350
static func encodeClaudeProjectDir(_ path: String) -> String {
// Claude derives a project directory name by replacing both "/" and "." with "-"
// (e.g. "/Users/x/repo/.claude" -> "-Users-x-repo--claude"). Missing the "." case
// sent dotted paths to the wrong project directory.
path.replacingOccurrences(of: "/", with: "-")
.replacingOccurrences(of: ".", with: "-")
}

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.

P1 Duplicate encodeClaudeProjectDir in SessionIndexStore.swift not updated

SessionIndexStore.swift line 877 has its own private copy of encodeClaudeProjectDir that still only replaces / with - and does not replace . with -. That copy is used as the cwdFilter fast path in enumerateClaudeJSONLCandidates: when a filter is set and the encoded name doesn't match Claude's actual directory on disk, the function returns an empty candidate list immediately (no full-enumeration fallback). For any session whose working directory contains a dot (e.g. /Users/foo.bar/project or /Users/foo/node_modules/.bin), the fast path would look for -Users-foo.bar-project while Claude stores the transcript under -Users-foo-bar-project, returning zero results. The fix in this file is correct but incomplete.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in d4c5021. Removed the duplicate encodeClaudeProjectDir in SessionIndexStore and routed the cwd-filter fast path to RestorableAgentSessionIndex.encodeClaudeProjectDir, so both discovery paths now encode "." -> "-" identically.

— Claude Code

Comment on lines +211 to +246

let configDir = root.appendingPathComponent("claude-config", isDirectory: true)
let projectsDir = configDir.appendingPathComponent("projects", isDirectory: true)
let launchCwd = root.appendingPathComponent("repo", isDirectory: true)
let driftedCwd = root.appendingPathComponent("worktree", isDirectory: true)
try fm.createDirectory(at: launchCwd, withIntermediateDirectories: true)
try fm.createDirectory(at: driftedCwd, withIntermediateDirectories: true)
try fm.createDirectory(
at: projectsDir.appendingPathComponent(
RestorableAgentSessionIndex.encodeClaudeProjectDir(launchCwd.path),
isDirectory: true
),
withIntermediateDirectories: true
)

let sessionId = "aaaaaaaa-aaaa-aaaa-aaaa-aaaaaaaaaaaa"
let workspaceId = UUID()
let panelId = UUID()
try writeClaudeTranscript(sessionId: sessionId, cwd: launchCwd, projectsDir: projectsDir)
let transcriptPath = projectsDir
.appendingPathComponent(
RestorableAgentSessionIndex.encodeClaudeProjectDir(launchCwd.path),
isDirectory: true
)
.appendingPathComponent("\(sessionId).jsonl", isDirectory: false)
.path

try writeClaudeHookStore(
root: root,
sessions: [
sessionId: driftedHookRecord(
sessionId: sessionId,
workspaceId: workspaceId,
panelId: panelId,
recordedCwd: driftedCwd.path,
launchCwd: launchCwd.path,

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.

P2 Tests don't exercise the dot-encoding fix in encodeClaudeProjectDir

Both new tests use path components that contain no dots ("repo", "worktree", "claude-config"), so encodeClaudeProjectDir's new . → - replacement is never exercised by the test suite. If the encoding assumption is wrong for user directories that do contain dots (e.g. /Users/alice.smith/code), the transcript-path matching and config-scan branches would silently fail to match, falling back to the drifted cwd and reproducing the original bug. A test case with dots in the launch-cwd path would directly verify the fix.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in d4c5021. The launch path now contains a "." (repo.main), so the "." -> "-" replacement is exercised by both tests.

— Claude Code

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit bd1e119. Configure here.

Comment thread Sources/RestorableAgentSession.swift

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/RestorableAgentSessionIndexTests.swift`:
- Around line 214-235: The test currently uses
RestorableAgentSessionIndex.encodeClaudeProjectDir(...) to create the fixture,
which masks regressions; instead hard-code the expected directory name in the
fixture by deriving it from the launchCwd path using the same external rule
(replace "." with "-" in the cwd component(s)) and use that value when creating
and referencing the on-disk Claude project directory and transcriptPath; update
the setup in RestorableAgentSessionIndexTests (variables launchCwd, driftedCwd,
projectsDir, sessionId and the calls to createDirectory and transcriptPath) and
leave the call to writeClaudeTranscript(sessionId:cwd:projectsDir:) unchanged so
the test asserts against an independently-computed expected project dir name
rather than the production helper encodeClaudeProjectDir.

In `@Sources/RestorableAgentSession.swift`:
- Around line 1344-1349: The duplicate project-dir encoding logic diverges:
update Sources/SessionIndexStore.swift to use the same encodeClaudeProjectDir
behavior (replace both "/" and ".") by extracting the logic into a single shared
implementation (e.g., a new static helper or extension) and have both
RestorableAgentSession.encodeClaudeProjectDir and the code in SessionIndexStore
call that shared function so dotted cwd paths resolve identically across both
discovery paths.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: bca8745b-2041-4975-aec8-9e40cf9cba98

📥 Commits

Reviewing files that changed from the base of the PR and between 1550bf7 and bd1e119.

📒 Files selected for processing (2)
  • Sources/RestorableAgentSession.swift
  • cmuxTests/RestorableAgentSessionIndexTests.swift

Comment thread cmuxTests/RestorableAgentSessionIndexTests.swift Outdated
Comment thread Sources/RestorableAgentSession.swift

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 2 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Sources/RestorableAgentSession.swift">

<violation number="1" location="Sources/RestorableAgentSession.swift:1349">
P1: This updates Claude project-dir encoding in only one code path. If transcript discovery still uses a slash-only encoder elsewhere, dotted cwd values will resolve to different project directory names and valid Claude transcripts can be missed. Please single-source this encoding logic (or update the other encoder) so lookup paths stay consistent.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

// (e.g. "/Users/x/repo/.claude" -> "-Users-x-repo--claude"). Missing the "." case
// sent dotted paths to the wrong project directory.
path.replacingOccurrences(of: "/", with: "-")
.replacingOccurrences(of: ".", with: "-")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1: This updates Claude project-dir encoding in only one code path. If transcript discovery still uses a slash-only encoder elsewhere, dotted cwd values will resolve to different project directory names and valid Claude transcripts can be missed. Please single-source this encoding logic (or update the other encoder) so lookup paths stay consistent.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Sources/RestorableAgentSession.swift, line 1349:

<comment>This updates Claude project-dir encoding in only one code path. If transcript discovery still uses a slash-only encoder elsewhere, dotted cwd values will resolve to different project directory names and valid Claude transcripts can be missed. Please single-source this encoding logic (or update the other encoder) so lookup paths stay consistent.</comment>

<file context>
@@ -1279,7 +1342,11 @@ struct RestorableAgentSessionIndex: Sendable {
+        // (e.g. "/Users/x/repo/.claude" -> "-Users-x-repo--claude"). Missing the "." case
+        // sent dotted paths to the wrong project directory.
         path.replacingOccurrences(of: "/", with: "-")
+            .replacingOccurrences(of: ".", with: "-")
     }
 
</file context>

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Fixed in d4c5021. Encoding is now single-sourced across both discovery paths, so dotted cwd values resolve to the same project directory name everywhere.

— Claude Code

Comment thread cmuxTests/RestorableAgentSessionIndexTests.swift Outdated

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: af00287d34

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

// (e.g. "/Users/x/repo/.claude" -> "-Users-x-repo--claude"). Missing the "." case
// sent dotted paths to the wrong project directory.
path.replacingOccurrences(of: "/", with: "-")
.replacingOccurrences(of: ".", with: "-")

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve dots when deriving Claude project dirs

For Claude sessions launched from a path containing ., this rewrite makes the transcript lookup use repo-v2 instead of Claude's existing repo.v2 project directory format (the other Claude locator in Sources/SessionIndexStore.swift still documents and implements slash-to-dash only). In the drifted-cwd case this means neither the transcriptPath match nor the config scan finds the launch cwd, so the code falls back to the drifted record.cwd and claude --resume/--fork-session still fails for dotted project paths.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not applicable — this is contradicted by Claude's actual on-disk behavior. Claude encodes project directories by replacing BOTH / and . with -, including middle dots. Verified against 10 real project dirs under this machine's CLAUDE_CONFIG_DIR; e.g. cwd /private/tmp/cmux-claude-subagent-probe.LIwVJX/project is stored at -private-tmp-cmux-claude-subagent-probe-LIwVJX-project (the middle . became -), and /Users/.../cmuxterm-hq/.claude/worktrees/... is stored at -Users-...-hq--claude-worktrees-.... For every dotted-cwd case, slash-only encoding does NOT match the real dir and slash+dot encoding does. The slash-only encoder this comment references was the pre-existing bug; this PR fixes it and single-sources the encoder so both discovery paths agree.

— Claude Code

Address review feedback (CodeRabbit, Greptile, Cursor, cubic):

- SessionIndexStore had its own private encodeClaudeProjectDir that only replaced
  "/", so the cwd-filter fast path in enumerateClaudeJSONLCandidates (which early-
  returns) looked in the wrong project directory for any dotted cwd and could miss
  every transcript for that cwd. Removed the duplicate and route the one call site
  to RestorableAgentSessionIndex.encodeClaudeProjectDir so both discovery paths
  encode "." -> "-" identically.

- The new regression tests built their on-disk fixtures with the production
  encodeClaudeProjectDir, so a regression in that helper would have been masked,
  and used dot-free paths so the "." -> "-" contract was never exercised. They now
  place fixtures using an independently-computed project-dir name and a launch path
  containing a ".", so they fail if the encoder regresses.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@lawrencecchen
lawrencecchen merged commit 8b63e8d into main Jun 2, 2026
41 checks passed
@lawrencecchen
lawrencecchen deleted the feat-claude-fork-cwd-drift branch June 2, 2026 02:33
@austinywang austinywang mentioned this pull request Jun 2, 2026
lawrencecchen added a commit that referenced this pull request Jun 3, 2026
Agent session stores are namespaced by the directory the agent was
launched in; cmux was resuming from the agent's drifted runtime cwd (a
worktree it cd'd into mid-session), so `<agent> --resume <id>` looked in
the wrong namespace and failed with "No conversation found with session
ID". Two independent paths carried the drift, fixed via one shared engine:

New shared package logic (Packages/CMUXAgentLaunch/AgentResumeWorkingDirectory):
classifies each agent kind (cwd-in-file vs directory-namespaced) and resolves
the resume working directory, preferring the launch cwd for directory-
namespaced agents (Claude, Gemini, Cursor, Grok, Pi, Qoder) and keeping the
runtime cwd for id-keyed agents (Codex, OpenCode, Amp, ...). Linked by both
the app and the standalone cmux-cli, so there is one source of truth.

1. CLI surface resume binding (publishAgentSurfaceResumeBinding) — the path
   that re-runs the agent on a restored surface — now pins the binding to the
   launch cwd via the shared resolver instead of baking the drifted runtime
   cwd. This is the path that actually failed on a build that already had the
   Claude-only #5154 fix.
2. App-side resolver (restorableWorkingDirectory) generalized beyond Claude
   via RestorableAgentKind.cwdNamespacing, which now delegates to the shared
   classifier. Claude still verifies against the transcript first.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

This branch was successfully deployed

1 active deployment
Preview – cmux — d4c5021b Deployed Jun 2, 2026 by vercel[bot]
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