Skip to content

Fix Claude restore cwd drift for session resume - #6205

Merged
austinywang merged 50 commits into
mainfrom
issue-6194-session-restore-resume-cwd
Jun 19, 2026
Merged

austinywang merged 50 commits into
mainfrom
issue-6194-session-restore-resume-cwd

Conversation

@austinywang

@austinywang austinywang commented Jun 16, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Testing

  • git show --check --stat --oneline HEAD && git show --check --stat --oneline HEAD~1
  • Not run: local test suite per task instructions.

Fixes #6194


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag /codesmith with what you need. Autofix is disabled.


Summary by cubic

Fix Claude session resume when cwd/workspace/surface or CLAUDE_CONFIG_DIR drift, and preserve auth selection on resume. Restores reliably return to the intended conversation and avoid “No conversation found”. Fixes #6194.

  • Bug Fixes
    • Retarget agent‑hook resume bindings to the snapshot’s restorable cwd (fallback to launch cwd); non‑agent bindings unchanged.
    • Surface resolution: strict --surface with localized errors; invalid explicit → mapped session else error. No flags: mapped session > Claude process binding (TTY, then PID with socket auth) > caller TTY > env. Ambient TTY is ignored when workspace/surface flags are set; stale TTY workspace bindings are ignored.
    • Session start prefers the Claude TTY over leaked env; if no TTY marker exists, PID binding is used. Persist workspace_id.
    • cmux-claude-wrapper: on --resume <id>, self‑heal CLAUDE_CONFIG_DIR to the root that holds the transcript only when the current root lacks it; validate the id token before searching; bound the resume lookup to avoid scanning unrelated paths; tolerate value flags before --resume; stop parsing at prompt text and after --; preserve safe auth selection values on resume; define the resume parser before passthrough.
    • Workspace: drop incompatible restored agent snapshots when the resume binding changes to prevent mismatched restores.
    • zsh integration reinstalls the claude/grok wrappers after startup so user functions don’t override wrapper dispatch.

Written for commit ac299ea. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • New Features / Improvements
    • Improved terminal session restoration for agent-hook resumes by retargeting the restored working directory to match the launch context, even when the runtime working directory has drifted.
    • Enhanced Claude hook surface resolution so an explicitly provided --surface reliably overrides conflicting environment and TTY-derived candidates, with clearer and more deterministic fallback behavior.
  • Tests
    • Added regression coverage for agent-hook auto-resume working-directory normalization and session-start surface binding precedence (including leaked ambient values and invalid explicit --surface).

@vercel

vercel Bot commented Jun 16, 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 19, 2026 9:06pm
cmux-staging Building Building Preview, Comment Jun 19, 2026 9:06pm

@coderabbitai

coderabbitai Bot commented Jun 16, 2026 •

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds CWD-drift correction for agent-hook resume bindings during session restore. A new replacingRequiredChangeDirectoryPrefix overload strips the previous cd prefix via previousWorkingDirectory before reapplying for a target directory, a new retargetingWorkingDirectory method applies it to snapshot commands, and a new resumeBindingForSessionRestore helper wires the resolution together at restore time. Separately, surface argument resolution is refactored to distinguish explicitly provided --surface flags from environment defaults and adjust priority accordingly.

Changes

CWD Drift Correction for Agent-Hook Resume Bindings

Layer / File(s) Summary
Prefix rewrite overload and snapshot retargeting method
Sources/RestorableAgentSession.swift, Sources/SessionPersistence.swift
Adds replacingRequiredChangeDirectoryPrefix(in:previousWorkingDirectory:workingDirectory:) that strips the previous cd prefix then reapplies for the new directory; adds SurfaceResumeBindingSnapshot.retargetingWorkingDirectory(_:) that uses it to rewrite command and cwd when source == "agent-hook".
Workspace session restore integration
Sources/Workspace.swift
Adds resumeBindingForSessionRestore static helper that resolves the correct working directory via AgentResumeWorkingDirectory().resolve(...) and retargets the binding when CWD differs; updates createPanel restore path to use it instead of the raw snapshot binding.
Drift-CWD restore test and extended assertion helper
cmuxTests/AgentSessionAutoResumeSettingsTests.swift
Adds testClaudeAgentHookResumeBindingRestoresFromLaunchCwdWhenRuntimeCwdDrifted asserting the startup script uses the launch CWD and excludes the drifted CWD; extends assertAgentAutoResumeUsesStartupCommand with an excludedNeedles parameter.

Surface Argument Handling and Resolution Priority Refactor

Layer / File(s) Summary
Explicit surface flag tracking and resolution priority reordering
CLI/cmux.swift
Extracts the explicit --surface flag into hookSurfaceFlag and threads a fallbackIsExplicit parameter through surface resolution helpers (resolvePreferredSurfaceIdForClaudeHook, resolvePreferredSurfaceForClaudeHookDetailed, resolveSurfaceAllowingFallbackDetailed). When the fallback is explicit, strict resolution attempts it first; when not explicit, caller TTY surface is prioritized over raw fallback.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

  • Directly addresses manaflow-ai/cmux#6194: Session restore intermittently fails when the runtime CWD has drifted from the launch CWD; this PR fixes it by retargeting the resume binding to the original launch working directory.

Possibly related PRs

  • manaflow-ai/cmux#4859: Modifies the same TerminalStartupWorkingDirectoryPrefix and SurfaceResumeBindingSnapshot command prefix transformation pipeline that this PR extends with the previousWorkingDirectory-aware overload.
  • manaflow-ai/cmux#5300: Directly related at the resume-binding cwd/command level—changes how working directory is selected (preferring launch cwd for directory-namespaced agents) when constructing resume bindings.
  • manaflow-ai/cmux#4237: Introduces surface resume bindings restore behavior that this PR extends with CWD retargeting and implicit-vs-explicit surface flag handling.

Poem

🐇 A bunny hopped between two paths one day,
The cwd had drifted far astray.
"No conversation found!" Claude cried in vain,
But now we strip the old cd and try again.
The launch dir leads the way, the session found,
Hop safely home—on familiar ground! 🌿


Important

Pre-merge checks failed

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

❌ Failed checks (5 errors, 1 warning)

Check name Status Explanation Resolution
Cmux Swift Concurrency ❌ Error The PR introduces fire-and-forget Task.detached in Workspace.swift with real lifecycle (race protection via refreshID tracking) that is neither stored, cancelled, nor tied to caller-owned operation... Replace fire-and-forget Task.detached pattern with stored task property so cancellation/lifecycle can be managed explicitly.
Cmux Swiftpm Lockfiles ❌ Error 79 cmux-owned Package.swift files added but only 12 Package.resolved files provided; 68 packages missing lockfile diffs violating SwiftPM package-resolved policy. Add missing Package.resolved files for all 68 cmux-owned packages that have Package.swift with dependencies, to ensure SwiftPM dependency pins are visible in PR diffs per policy.
Cmux User-Facing Error Privacy ❌ Error The PR's modified session restoration flow ensures agent-hook resume bindings (containing claude --resume <sessionId>) are passed to the user-facing alert displayed in shouldRunPromptedSurfaceRes... Sanitize binding.command before display in shouldRunPromptedSurfaceResumeOnMain to redact or omit sensitive identifiers like session IDs from the user-facing alert message.
Cmux Full Internationalization ❌ Error New user-facing Swift text in CLI/cmux.swift uses String(localized:) with keys (cli.codex-teams.usage, cli.omx.usage, cli.omc.usage) that are missing from Resources/Localizable.xcstrings. Add missing localization keys to Resources/Localizable.xcstrings with complete translations for all 20 supported locales (en, ja, zh-Hans, zh-Hant, ko, de, es, fr, it, ar, bs, da, nb, pl, pt-BR, ru, th, tr, uk, km).
Cmux Source Artifacts ❌ Error The PR adds local Claude AI workspace artifacts (.claude/scheduled_tasks.lock, .claude/skills/*, .claude/commands/*, .agents/skills) containing tool output, session metadata, and generated... Remove .claude/ and .agents/ directories from the commit and add .claude/ entry to .gitignore to exclude local Claude workspace state from source control.
Docstring Coverage ⚠️ Warning Docstring coverage is 4.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (16 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main change: fixing Claude session restore when the working directory has drifted.
Linked Issues check ✅ Passed The PR comprehensively addresses #6194 by implementing the two main fixes: normalizing restored agent-hook bindings through AgentResumeWorkingDirectory to resolve from launch cwd, and updating surface selection precedence with proper tests.
Out of Scope Changes check ✅ Passed All changes are directly scoped to fixing Claude session restore failures: agent binding normalization, working directory retargeting, surface selection logic, and regression tests for these specific issues.
Cmux Swift Actor Isolation ✅ Passed All production Swift changes follow Swift 6 actor isolation guidelines: nonisolated enum/struct with static/instance methods returning Sendable value types (RestorableAgentSession, SessionPersisten...
Cmux Swift Blocking Runtime ✅ Passed No blocking/timing-based synchronization patterns (semaphores, waits, sleeps, etc.) were introduced in production code. Test files use deterministic test-only scaffolding semaphores (permitted).
Cmux Expensive Synchronous Load ✅ Passed New methods only perform string operations and struct initialization with in-memory data; no expensive synchronous loaders (RestorableAgentSessionIndex.load, sysctl, disk I/O) added to MainActor/in...
Cmux Cache Substitution Correctness ✅ Passed PR properly handles cache substitution: cached restorableAgent from SharedLiveAgentIndex is acceptable with cold-cache fallback (returns binding unchanged if nil) and documented graceful degradat...
Cmux No Hacky Sleeps ✅ Passed PR modifies only Swift files (.swift), which are explicitly out of scope for runtime-no-hacky-sleeps.md. Swift timing primitives are covered by swift-blocking-runtime.md per the rule.
Cmux Algorithmic Complexity ✅ Passed Code changes operate on bounded inputs (single commands, single bindings per panel, single hook invocations) or make one-time socket calls; no nested full-collection scans on user-owned records or...
Cmux Swift @Concurrent ✅ Passed All new methods are synchronous pure helpers (no async functions added); no @concurrent annotations introduced or violated. Complies with swift-concurrent-annotation.md rules.
Cmux Swift File And Package Boundaries ✅ Passed PR respects swift-file-package-boundaries: no new oversized files; additions to existing large files (Workspace +27, CLI/cmux +79) are under 250-line threshold; all changes maintain coherent single...
Cmux Swift Logging ✅ Passed No logging violations found in production Swift code. New methods in RestorableAgentSession, SessionPersistence, Workspace, and CLI changes contain no print, debugPrint, dump, or NSLog statements.
Cmux Swiftui State Layout ✅ Passed All changes are pure functions without new SwiftUI state patterns, GeometryReader layout changes, lazy list store references, or render-time mutations. Methods are called from model lifecycle conte...
Cmux Architecture Rethink ✅ Passed PR implements a clean correctness fix for #6194 with clear ownership and invariants. No timing/blocking repair paths, new mutable state, multiple surfaces, or split lifecycle ownership. Addresses r...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR does not add or materially change NSWindow/NSPanel/NSWindowController or SwiftUI Window/WindowGroup. Changes are to session restoration and CLI logic only.
Description check ✅ Passed The pull request description covers the key required sections: Summary (what changed and why), Testing (how tested), and includes relevant issue reference #6194.
✨ 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 issue-6194-session-restore-resume-cwd

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 16, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes Claude session resume failures caused by cwd/CLAUDE_CONFIG_DIR drift. When a cmux session is restored from a different working directory or a foreign config root, claude --resume previously reported "No conversation found with session ID" because the agent-hook binding's cwd and command prefix pointed to the old runtime directory and the transcript lived under a different config root.

  • SessionPersistence.swift / Workspace.swift: resumeBindingForSessionRestore retargets agent-hook binding cwd and command prefix through AgentResumeWorkingDirectory to the snapshot's recorded workingDirectory/launchCommand.workingDirectory instead of the drifted runtime cwd; stale agent snapshots (mismatched checkpointId or kind) are cleared so they don't carry over to a different agent session.
  • cmux-claude-wrapper: On --resume <id>, the wrapper self-heals CLAUDE_CONFIG_DIR to the config root that actually holds the transcript; also fixes preserved Claude auth selection values being incorrectly removed before passthrough.
  • CLI/cmux.swift: Surface resolution refactored to give explicit --surface strict priority, suppress leaked ambient TTY when workspace/surface flags are set, and add PID-based fallback binding for session-start hooks.

Confidence Score: 4/5

The core cwd-retargeting and CLAUDE_CONFIG_DIR self-healing logic is well-scoped and thoroughly tested. A known pre-existing issue in the surface-resolution path (an internal surface UUID can appear in a user-facing error under a specific stale-explicit-surface scenario) carries over from the previous review cycle.

The main production paths — retargeting bindings on restore, clearing stale agent snapshots, self-healing CLAUDE_CONFIG_DIR in the wrapper, and the surface-resolution precedence refactor — are all covered by new regression tests. The one outstanding concern is in resolvePreferredSurfaceForClaudeHookDetailed: when an explicit --surface fails and the code falls back to resolveStrictSurfaceForClaudeHookDetailed(preferred, …) with an internal session-mapping UUID, a failure there emits 'Surface not found: ' rather than an error referencing the user-supplied --surface value. This was flagged in the prior review cycle and has not been addressed in this PR.

CLI/cmux.swift — the resolvePreferredSurfaceForClaudeHookDetailed fallback to resolveStrictSurfaceForClaudeHookDetailed(preferred, …) can produce an error message containing an internal surface UUID when both the explicit --surface and the mapped session surface are stale.

Important Files Changed

Filename Overview
CLI/cmux.swift Large surface-resolution refactor: adds fallbackIsExplicit, preferCallerTTYRouting, and a lazy callerTTYBinding() closure with PID-based fallback; adds resolveStrictSurfaceForClaudeHookDetailed for explicit --surface; adds workspace_id to resume-binding publish params. The known pre-existing issue (internal surface UUID exposed in 'Surface not found' error when both explicit and mapped surfaces are stale) remains.
Sources/Workspace.swift Adds resumeBindingForSessionRestore and restorableAgentForSessionRestore (both nonisolated) to filter stale agents and retarget bindings at snapshot time; correctly threads resumeBinding through both sessionPanelSnapshot and addPanelFromSnapshot.
Sources/SessionPersistence.swift Adds retargetingWorkingDirectory(_:) on SurfaceResumeBindingSnapshot to rewrite the cwd field and cd -- command prefix atomically; correctly guards on isAgentHookBinding before any mutation.
Resources/bin/cmux-claude-wrapper Adds reconcile_claude_config_dir_for_resume (with safe session-ID validation and bounded glob depth) and extract_claude_resume_session_id (correct bash 0-indexed array parsing); correctly moves claude_option_consumes_value before early-exit blocks so the parser is available everywhere.
Sources/RestorableAgentSession.swift Adds replacingRequiredChangeDirectoryPrefix for atomic prefix swap; extends newestClaudeTranscript to recurse up to depth 4 covering the nested sessionId/messages/sessionId.jsonl layout; adds claudeTranscriptPath with caching in ClaudeTranscriptLookupCache.
Resources/shell-integration/cmux-zsh-integration.zsh Reinstalls claude/grok wrapper functions in _cmux_fix_path (first-precmd hook) so user dotfile overrides are evicted after all init files have run.
cmuxTests/AgentSessionAutoResumeSwiftTests.swift New test suite covering drifted cwd retargeting, stale agent snapshot eviction, cross-kind binding filtering, hibernation state clearing, and nested transcript index lookup.
cmuxTests/ClaudeHookSurfaceResolutionSwiftTests.swift New test suite for surface resolution precedence: TTY over leaked env, PID binding fallback, stale TTY workspace suppression, explicit --surface override, and mapped-session-over-TTY priority.
tests/test_claude_wrapper_hooks.py Adds socket_state parameter and multiple self-heal resume scenarios; verifies CLAUDE_CONFIG_DIR is correctly repointed when the transcript lives in a different config root.

Reviews (30): Last reviewed commit: "fix: bound Claude resume resolution" | Re-trigger Greptile

Comment thread Sources/SessionPersistence.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: 1

🤖 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/CLINotifyProcessIntegrationRegressionTests.swift`:
- Around line 297-415: In the test function
testClaudeMappedSessionOverridesTTYBindingWithoutExplicitSurface, the
CMUX_SURFACE_ID environment variable is currently set to mappedSurfaceId, which
is the same value the test expects to win. To prevent regressions where the code
simply reuses the caller's environment, change CMUX_SURFACE_ID to a distinct
ambient surface ID (different from both mappedSurfaceId and ttySurfaceId) so
that the test properly verifies the persisted mapped surface is selected over
the ambient caller surface. Keep all existing assertions unchanged so they
continue to verify the mapped surface is correctly chosen when the ambient
surface differs.
🪄 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: eb81d22e-a58a-4cc6-8022-4212591a1ebf

📥 Commits

Reviewing files that changed from the base of the PR and between 80250aa and b05b804.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (2)
  • CLI/cmux.swift
  • cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift

Comment thread cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift Outdated
austinywang and others added 3 commits June 19, 2026 02:38
When cmux is launched with a foreign CLAUDE_CONFIG_DIR (e.g. the .app is
opened from a terminal whose agent set one), a restored
`claude --resume <id>` resumes against the wrong config root and reports
"No conversation found", dropping the user to a bare shell (#6194).

This test fails until the wrapper self-heals CLAUDE_CONFIG_DIR to the
config root that actually holds the transcript.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A restored `claude --resume <id>` only resolves a session under the
current CLAUDE_CONFIG_DIR. When the cmux app inherits a foreign
CLAUDE_CONFIG_DIR (e.g. the .app is opened by cmd-clicking a link from a
terminal whose agent set one), it propagates that dir to every restored
pane, so sessions created under a different config root resume against
the wrong namespace and fail with "No conversation found" — leaving the
user at a bare shell instead of their conversation (#6194).

The wrapper now relocates CLAUDE_CONFIG_DIR to the config root that
actually holds the transcript when resuming an explicit session id, and
only when the current root lacks it (a correct resume is never
repointed). Session ids are filename-token validated before any
glob-walk.

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

This branch was successfully deployed

1 active deployment
Preview – cmux — ac299ea2 Deployed Jun 19, 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.

Session restore intermittently fails: claude --resume reports "No conversation found" and the SessionEnd hook is cancelled

2 participants