Skip to content

Issue 6194 session restore resume cwd - #6458

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

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

Conversation

@austinywang

@austinywang austinywang commented Jun 19, 2026 •

Copy link
Copy Markdown
Contributor

Summary

  • What changed?
  • Why?

Testing

  • How did you test this change?
  • What did you verify manually?

Demo Video

For UI or behavior changes, include a short demo video (GitHub upload, Loom, or other direct link).

  • Video URL or attachment:

Review Trigger (Copy/Paste as PR comment)

@codex review
@coderabbitai review
@greptile-apps review
@cubic-dev-ai review

Checklist

  • I tested the change locally
  • I added or updated tests for behavior changes
  • I updated docs/changelog if needed
  • I requested bot reviews after my latest commit (copy/paste block above or equivalent)
  • All code review bot comments are resolved
  • All human review comments are resolved

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


Summary by cubic

Fixes Claude session restore so resumed sessions open in the original working directory and recover when CLAUDE_CONFIG_DIR doesn’t match the transcript location. Hardens hook routing and wrapper/shim resolution so Claude starts in the right workspace, recovers from stale wrappers/aliases in bash/zsh/fish, and avoids bare-shell drops. Addresses issue 6194.

  • Bug Fixes

    • Auto-resume retargets the working directory for Claude agent-hook sessions to the launch cwd; non-agent bindings are unchanged.
    • Wrapper self-heals CLAUDE_CONFIG_DIR for claude --resume <id> when the current root lacks the transcript; validates ids, bounds the search, preserves auth selection, and stops at prompt text without injecting a session id.
    • Hook routing accepts the caller TTY binding only if its surface is listed; otherwise falls back to the listed Claude PID binding, ignores leaked env, and localizes errors for invalid or missing surfaces.
    • Keeps the mapped Claude session surface authoritative and blocks fall-through when an explicit surface is invalid.
    • Shell shims and terminal startup resolve the wrapper from the current bundle or the installed cmux’s sibling; if missing or reaped, strip inherited cmux shim roots from PATH and exec the user’s claude. Shell claude() functions route through a shim resolver and recover after aliases/functions and late PATH changes (bash/zsh/fish).
    • Wrapper treats cmux-cli-shims/claude as self to avoid recursion.
  • Tests

    • New Swift test for stale TTY fallback to the Claude PID binding.
    • Swift tests for Ghostty shim fallback to the current bundle and for skipping inherited cmux CLI shim roots.
    • Shell integration tests for zsh, bash, and fish ensure claude() uses the shim resolver and falls back to the current bundle when the original wrapper is reaped.
    • Python tests cover prompt-text resume parsing (no session id injection) and skipping inherited cmux CLI shim roots.

Written for commit 9ff87f1. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Improved fallback resolution for Claude command wrapper when cached configurations become stale or unavailable.
    • Enhanced session resumption to automatically detect and correct mismatched configuration directories.
    • Refined terminal binding validation to ensure proper workspace surface resolution for Claude hooks.
  • Tests

    • Added comprehensive test coverage for Claude hook surface resolution and session resume recovery scenarios.
    • Expanded wrapper dispatch testing across multiple shell environments.

austinywang and others added 30 commits June 15, 2026 17:30
…store-resume-cwd

# Conflicts:
#	.github/swift-file-length-budget.tsv
…store-resume-cwd

# Conflicts:
#	.github/swift-file-length-budget.tsv
austinywang and others added 16 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>
…store-resume-cwd

# Conflicts:
#	.github/swift-file-length-budget.tsv
@vercel

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

@chatgpt-codex-connector

Copy link
Copy Markdown

To use Codex here, create a Codex account and connect to github.

@coderabbitai

coderabbitai Bot commented Jun 19, 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: b255b09a-efe0-4c9f-bd45-c6d33a756c84

📥 Commits

Reviewing files that changed from the base of the PR and between bfe42d8 and 9ff87f1.

⛔ Files ignored due to path filters (1)
  • .github/swift-file-length-budget.tsv is excluded by !**/*.tsv
📒 Files selected for processing (5)
  • CLI/cmux.swift
  • Resources/shell-integration/cmux-bash-integration.bash
  • Resources/shell-integration/cmux-zsh-integration.zsh
  • Resources/shell-integration/fish/config.fish
  • tests/test_issue_2448_shell_claude_wrapper_dispatch.py

📝 Walkthrough

Walkthrough

Adds validated TTY/agent caching to the callerTTYBinding() resolver, replaces unconditional wrapper exec in all shell shims (bash, zsh, fish, macOS) with conditional wrapper resolution and PATH-filtered claude fallback, adds CLAUDE_CONFIG_DIR self-healing in cmux-claude-wrapper for mismatched --resume sessions, and refactors ClaudeTranscriptLookupCache with keyed path caches and new public lookup methods.

Changes

Claude Wrapper Fallback, Resume Self-Healing, TTY Cache Validation & Transcript Cache Refactor

Layer / File(s) Summary
TTY binding cache validation
CLI/cmux.swift
callerTTYBinding() now separately resolves TTY and agent-process binding candidates and validates each against claudeHookSurfaceIsListed before caching, replacing the prior ?? fallback assignment.
Shell shim conditional wrapper resolution with PATH-filtered fallback
Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Spawn/TerminalSurface+StartupEnvironment.swift, Resources/shell-integration/cmux-bash-integration.bash, Resources/shell-integration/cmux-zsh-integration.zsh, Resources/shell-integration/fish/config.fish, Resources/bin/cmux-claude-wrapper
All Claude shim generators replace unconditional wrapper exec with: resolve cmux-claude-wrapper via CMUX_BUNDLED_CLI_PATH or cmux on PATH; exec it when found; otherwise strip shim directories from PATH and exec claude directly. Each shell also gains a _cmux_claude_wrapper_command resolver. The wrapper's self/shim detector is extended to also skip cmux-cli-shims paths.
CLAUDE_CONFIG_DIR self-healing in cmux-claude-wrapper
Resources/bin/cmux-claude-wrapper
Adds cmux_claude_config_dir_has_session, flag-parsing helpers (extract_claude_resume_session_id, claude_explicit_session_flag_present, reconcile_claude_config_dir_for_resume), integrates reconciliation into hooks-disabled and socket-unavailable paths, removes a duplicate function definition, and replaces the SKIP_SESSION_ID manual scan.
ClaudeTranscriptLookupCache refactor and transcript path resolution
Sources/RestorableAgentSession.swift, Sources/SessionIndexModels.swift
replacingRequiredChangeDirectoryPrefix gains previousWorkingDirectory; newestClaudeTranscript refactored to inout best accumulator; claudeTranscriptExists queries ClaudeTranscriptLookupCache; new claudeProjectDirName helper and transcriptPath/transcriptPathInAnyProject public cache methods added with keyed path caches.
Swift tests: Claude hook surface resolution
cmuxTests/ClaudeHookSurfaceResolutionSwiftTests.swift
New serialized test suite validates session-start and prompt-submit routing (TTY vs mapped session vs explicit --surface) through a concurrency-based mock Unix socket server implementing surface.list, debug.terminals, system.top, feed.push, and surface.resume.set.
Swift tests: shim fallback to current bundled wrapper
cmuxTests/GhosttyTerminalStartupEnvironmentTests.swift
Two new tests verify: fallback to the current bundled wrapper when the embedded wrapper is deleted, and that inherited cmux-cli-shims PATH entries are skipped in favor of the real claude binary.
Python tests: resume self-healing, shim-root skipping, stale wrapper dispatch
tests/test_claude_wrapper_hooks.py, tests/test_claude_wrapper_user_binary_resolution.py, tests/test_issue_2448_shell_claude_wrapper_dispatch.py
run_wrapper_auth_env gains socket_state/in_cmux params; adds 10 resume self-heal regression tests and a shim-root skipping test; stale-wrapper helpers and current-bin executables added for zsh/bash/fish with corresponding assertions.

Sequence Diagram(s)

sequenceDiagram
  participant Shell as Shell (bash/zsh/fish)
  participant Shim as claude shim
  participant Wrapper as cmux-claude-wrapper
  participant Extract as extract_claude_resume_session_id
  participant HasSession as cmux_claude_config_dir_has_session
  participant Reconcile as reconcile_claude_config_dir_for_resume
  participant Claude as real claude

  Shell->>Shim: claude --resume <sid>
  alt cmux-claude-wrapper found (via CMUX_BUNDLED_CLI_PATH or cmux on PATH)
    Shim->>Wrapper: exec cmux-claude-wrapper --resume <sid>
    Wrapper->>Extract: argv
    Extract-->>Wrapper: session_id
    Wrapper->>HasSession: CLAUDE_CONFIG_DIR + session_id
    HasSession-->>Wrapper: not found
    Wrapper->>Reconcile: session_id
    Reconcile->>HasSession: each known config root
    HasSession-->>Reconcile: first match
    Reconcile-->>Wrapper: updated CLAUDE_CONFIG_DIR
    Wrapper->>Claude: exec real claude --resume <sid>
  else no wrapper available
    Shim->>Shim: strip cmux shim dirs from PATH
    Shim->>Claude: exec claude --resume <sid>
  end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related issues

Possibly related PRs

  • manaflow-ai/cmux#4116: Both PRs address CLAUDE_CONFIG_DIR handling in Claude resume flows — #4116 injects it from session-indexed config, this PR self-heals it at wrapper execution time.
  • manaflow-ai/cmux#5300: Both PRs modify claudeVerifiedRestorableWorkingDirectory and transcript presence checks in Sources/RestorableAgentSession.swift for CWD/project-dir drift.
  • manaflow-ai/cmux#5721: Both PRs change how resumed claude is routed through the wrapper shim token and cmux-claude-wrapper resume execution path.

Suggested reviewers

  • lawrencecchen

Poem

🐇 Hop hop, the shim now checks before it leaps,
No stale old wrapper wakes it from its sleeps.
If CLAUDE_CONFIG_DIR has gone astray,
The wrapper heals the path and finds the way.
The TTY is validated, cached just right —
Resume "No conversation found"? Not tonight! 🌙


Important

Pre-merge checks failed

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

❌ Failed checks (1 error, 2 warnings)

Check name Status Explanation Resolution
Cmux Full Internationalization ❌ Error PR adds two new user-facing error strings (cli.claude-hook.error.invalidSurface, cli.claude-hook.error.surfaceNotFound) with incomplete translations (only en, ja of 19 required locales). Add translations for all 19 supported locales (ar, bs, da, de, es, fr, it, ko, nb, pl, pt-BR, ru, th, tr, uk, zh-Hans, zh-Hant) to both error strings in Resources/Localizable.xcstrings.
Description check ⚠️ Warning The PR description is entirely boilerplate template with no actual content filled in. All sections (Summary, Testing, Demo Video, Checklist) are empty placeholders with no information about what changed, why, or how it was tested. Complete the description by filling in all required sections: summarize changes and rationale, describe testing approach and manual verification, add demo video if applicable, and check off items in the checklist.
Docstring Coverage ⚠️ Warning Docstring coverage is 1.89% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (20 passed)
Check name Status Explanation
Title check ✅ Passed The title 'Issue 6194 session restore resume cwd' directly references the GitHub issue number and accurately describes the core change: fixing session restoration and working directory recovery during resume operations.
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 Swift changes properly follow actor isolation rules: RestorableAgentSession.swift correctly uses nonisolated for pure value types; SessionIndexModels.swift uses Sendable/Codable on value...
Cmux Swift Blocking Runtime ✅ Passed No new blocking/timing-based synchronization introduced in production Swift code. All modified production files contain only synchronous logic; test files properly use allowed test scaffolding.
Cmux Expensive Synchronous Load ✅ Passed ClaudeTranscriptLookupCache is private, only used within RestorableAgentSessionIndex.load(), and caches results to reduce expensive operations. No expensive loaders were added to main-actor or inte...
Cmux Cache Substitution Correctness ✅ Passed The cached callerTTYBinding is validated with claudeHookSurfaceIsListed before caching, and every subsequent use re-validates with fresh surface.list queries. Persisted workspaceId/surfaceId come f...
Cmux No Hacky Sleeps ✅ Passed No fixed sleeps, timeouts, polling loops, or wall-clock waits were added in non-Swift production code (shell scripts, JavaScript, TypeScript, or build/runtime scripts). The PR modifies shell integr...
Cmux Algorithmic Complexity ✅ Passed All algorithmic changes use fixed-size/bounded collections (max 2-3 items with O(1) lookups) or static configs; not in hot paths; existing session loop is linear traversal with cached lookups per r...
Cmux Swift Concurrency ✅ Passed PR introduces no legacy async patterns in production Swift code; test files properly use Dispatch for synchronization per guidelines.
Cmux Swift @Concurrent ✅ Passed All async functions in Swift changes use explicit Task.detached() boundaries for background work and have proper Sendable return types. No missing or invalid @concurrent annotations found.
Cmux Swift File And Package Boundaries ✅ Passed All Swift file changes comply with package boundaries rule: RestorableAgentSession.swift adds only 174 lines (under 250 threshold) with refactored existing responsibilities; CLI/cmux.swift touched...
Cmux Swiftpm Lockfiles ✅ Passed PR contains no Package.swift, Package.resolved, or .gitignore changes; SwiftPM lockfile rules do not apply.
Cmux Swift Logging ✅ Passed PR complies with swift-logging.md: Logger (os.log) uses proper subsystem/category/privacy labels; print() calls are CLI output (allowed); no violations of print/NSLog in app code, file-scoped Logge...
Cmux User-Facing Error Privacy ✅ Passed All user-facing error messages in production code comply with privacy rules. Messages reference only product names and generic terms, with no exposure of credentials, tokens, environment variables,...
Cmux Swiftui State Layout ✅ Passed PR contains no SwiftUI changes—only CLI, shell integration, data models, and tests. The custom check for SwiftUI state layout is not applicable.
Cmux Architecture Rethink ✅ Passed Swift changes are small correctness fixes with clear owners and invariants: callerTTYBinding validates surfaces before caching, wrapper resolution uses fallback patterns without timing/polling, rec...
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR contains no additions or material changes to user-visible NSWindow, NSPanel, NSWindowController, SwiftUI Window/WindowGroup. Changes are logic updates and test fixtures only.
Cmux Source Artifacts ✅ Passed All changed files are legitimate source code, tests, and tool configuration files. No local artifacts, build output, logs, or temporary directories violate the source-control-artifacts.md rule.
Cmux No Test Or Debug Seam In Production Source ✅ Passed No new test/debug seams added to production Swift sources. CLI/cmux.swift contains pre-existing test seams (debugUsageTextForTesting, debugFormatDebugTerminalsPayloadForTesting) unchanged from main...
✨ 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.

…store-resume-cwd

# Conflicts:
#	.github/swift-file-length-budget.tsv
#	CLI/cmux.swift
#	Resources/bin/cmux-claude-wrapper
#	Sources/RestorableAgentSession.swift
#	cmuxTests/ClaudeHookSurfaceResolutionSwiftTests.swift
#	tests/test_claude_wrapper_hooks.py
@greptile-apps

greptile-apps Bot commented Jun 19, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR fixes session-restore CWD regression (issue 6194) by hardening three coupled subsystems: hook surface routing, wrapper/shim resolution, and transcript-lookup caching.

  • Hook routing (CLI/cmux.swift): callerTTYBinding() now validates both the TTY binding and the Claude-PID process binding against claudeHookSurfaceIsListed before committing either; a stale TTY binding no longer silently wins over a live PID binding.
  • Wrapper/shim resolution (TerminalSurface+StartupEnvironment.swift, bash/zsh/fish integrations): Generated claude shims add a 3-stage fallback (current-bundle wrapper → installed cmux-relative wrapper → PATH-stripped real claude); cmux-claude-wrapper additionally recognizes cmux-cli-shims paths as self to prevent recursive dispatch, and the positional-arg scanner no longer bails early on prompt text before --resume.
  • Transcript cache (RestorableAgentSession.swift): ClaudeTranscriptLookupCache refactored from typed composite-key structs + a found/missing enum to flat [String: String] dictionaries plus Set<String> negatives with NUL-delimited keys — mechanically equivalent but simpler.

Confidence Score: 5/5

The changes are well-scoped, thoroughly tested, and address genuine failure modes without introducing new runtime risks.

All three touched subsystems have new or expanded test coverage in both Swift and Python. The wrapper positional-arg fix and shim fallback chain are exercised by the new prompt-text resume test, the inherited-shim-root skipping test, and stale-wrapper dispatch tests for zsh/bash/fish. The transcript cache refactoring is mechanical. The only structural note is that the 3-stage wrapper resolution is now emitted by four separate code-generation sites.

The four parallel shim generators (TerminalSurface+StartupEnvironment.swift, cmux-bash-integration.bash, cmux-zsh-integration.zsh, fish/config.fish) now each embed the same wrapper-resolution logic; future edits to the fallback chain need to be applied in all four places.

Important Files Changed

Filename Overview
CLI/cmux.swift Hook surface resolution: TTY binding now validated against claudeHookSurfaceIsListed; if stale, falls back to the Claude-PID process binding (also validated).
Resources/bin/cmux-claude-wrapper cmux-cli-shims paths recognized as self/shim to prevent recursive calls; return 1 removed from positional-arg case so scanning continues past prompt text before --resume.
Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Spawn/TerminalSurface+StartupEnvironment.swift Generated claude shim gains 3-stage wrapper fallback; same logic emitted verbatim by four code-generation sites (Swift, bash, zsh, fish).
Sources/RestorableAgentSession.swift ClaudeTranscriptLookupCache refactored from typed structs + CachedTranscriptPath enum to flat dictionaries + Set negatives with NUL-delimited keys. Mechanically equivalent.
Resources/shell-integration/cmux-bash-integration.bash Wrapper fallback logic added to generated shim and _cmux_claude_wrapper_command() helper; claude eval'd function now routes through the helper.
Resources/shell-integration/cmux-zsh-integration.zsh Identical change to bash integration: wrapper fallback logic added to the generated shim and _cmux_claude_wrapper_command() helper.
Resources/shell-integration/fish/config.fish Same wrapper fallback logic in fish shim generator; claude function now checks CMUX_CLAUDE_WRAPPER_SHIM, then wrapper_path, then command claude.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["User types claude ..."] --> B["Shell claude() function"]
    B --> C{CMUX_CLAUDE_WRAPPER_SHIM executable?}
    C -- yes --> D["exec shim"]
    C -- no --> E{_CMUX_CLAUDE_WRAPPER / wrapper_path executable?}
    E -- yes --> D
    E -- no --> F["command claude (finds shim in PATH)"]
    F --> D
    D --> G{Original wrapper executable?}
    G -- yes --> H["exec cmux-claude-wrapper"]
    G -- no --> I{CMUX_BUNDLED_CLI_PATH-relative wrapper?}
    I -- yes --> H
    I -- no --> J{command -v cmux wrapper?}
    J -- yes --> H
    J -- no --> K["Strip cmux shim dirs; exec real claude"]
    H --> L{CMUX_SURFACE_ID set?}
    L -- yes --> M["Inject --settings/--session-id; exec real claude"]
    L -- no --> N["Pass-through"]
    M --> O["claudeHookSurfaceIsListed check"]
    O --> P{TTY binding listed?}
    P -- yes --> R["Use TTY binding"]
    P -- no --> S{PID binding listed?}
    S -- yes --> T["Use PID binding"]
    S -- no --> U["callerTTYBindingCache = nil"]
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
flowchart TD
    A["User types claude ..."] --> B["Shell claude() function"]
    B --> C{CMUX_CLAUDE_WRAPPER_SHIM executable?}
    C -- yes --> D["exec shim"]
    C -- no --> E{_CMUX_CLAUDE_WRAPPER / wrapper_path executable?}
    E -- yes --> D
    E -- no --> F["command claude (finds shim in PATH)"]
    F --> D
    D --> G{Original wrapper executable?}
    G -- yes --> H["exec cmux-claude-wrapper"]
    G -- no --> I{CMUX_BUNDLED_CLI_PATH-relative wrapper?}
    I -- yes --> H
    I -- no --> J{command -v cmux wrapper?}
    J -- yes --> H
    J -- no --> K["Strip cmux shim dirs; exec real claude"]
    H --> L{CMUX_SURFACE_ID set?}
    L -- yes --> M["Inject --settings/--session-id; exec real claude"]
    L -- no --> N["Pass-through"]
    M --> O["claudeHookSurfaceIsListed check"]
    O --> P{TTY binding listed?}
    P -- yes --> R["Use TTY binding"]
    P -- no --> S{PID binding listed?}
    S -- yes --> T["Use PID binding"]
    S -- no --> U["callerTTYBindingCache = nil"]
Loading

Reviews (2): Last reviewed commit: "chore: refresh Swift file length budget ..." | Re-trigger Greptile

Comment on lines +141 to +159
if [[ -x "$cmux_wrapper" ]]; then
exec "$cmux_wrapper" "$@"
fi
cmux_path_without_shim=""
cmux_old_ifs="$IFS"
IFS=:
for cmux_entry in ${PATH:-}; do
if [[ "$cmux_entry" == "$CMUX_CLAUDE_WRAPPER_SHIM_ROOT" || "$cmux_entry" == */cmux-cli-shims/* || "$cmux_entry" == */cmux-cli-shims ]]; then
continue
fi
if [[ -z "$cmux_path_without_shim" ]]; then
cmux_path_without_shim="$cmux_entry"
else
cmux_path_without_shim="$cmux_path_without_shim:$cmux_entry"
fi
done
IFS="$cmux_old_ifs"
export PATH="$cmux_path_without_shim"
exec claude "$@"

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 Silent failure when no claude binary exists in stripped PATH

The final fallback strips all cmux shim directories from PATH and runs exec claude "$@". If the user has claude installed exclusively via a cmux-managed path (a non-shim location that also got filtered out, or a system where the real binary only lives inside one of the skipped directories), the exec will fail with a shell "not found" error and no cmux-specific message. The same pattern is duplicated across the bash/zsh/fish shell integrations, so the silent failure applies everywhere. Adding a short diagnostic printf before the final exec would make this fallback diagnosable without a debugger.

Comment thread CLI/cmux.swift
Comment on lines 22914 to 22941
func callerTTYBinding() -> CallerTerminalBinding? {
if !didResolveCallerTTYBinding {
didResolveCallerTTYBinding = true
callerTTYBindingCache = resolveCallerTerminalBindingByTTY(
let ttyBinding = resolveCallerTerminalBindingByTTY(
client: client,
includeAmbientTTY: workspaceArg == nil && surfaceArg == nil
) ?? resolveAgentProcessTerminalBinding(
pid: claudeAgentPID(from: ProcessInfo.processInfo.environment),
socketPath: client.socketPath,
socketPassword: socketPassword
)
if let ttyBinding,
claudeHookSurfaceIsListed(ttyBinding.surfaceId, workspaceId: ttyBinding.workspaceId, client: client) {
callerTTYBindingCache = ttyBinding
} else {
let processBinding = resolveAgentProcessTerminalBinding(
pid: claudeAgentPID(from: ProcessInfo.processInfo.environment),
socketPath: client.socketPath,
socketPassword: socketPassword
)
if let processBinding,
claudeHookSurfaceIsListed(
processBinding.surfaceId,
workspaceId: processBinding.workspaceId,
client: client
) {
callerTTYBindingCache = processBinding
}
}
}
return callerTTYBindingCache
}

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 callerTTYBindingCache stays nil when TTY binding is stale and process binding is also not listed

The new code validates both ttyBinding and processBinding against claudeHookSurfaceIsListed. When the TTY binding exists but its surface is not listed (stale), the code falls through to processBinding. If processBinding is also non-nil but its surface is also not listed (e.g., the socket is transiently unavailable during the listing check), callerTTYBindingCache remains nil and the hook call is silently dropped. Previously the code would have used the stale TTY binding. The intentional "block fall-through on invalid surface" design is sound, but it introduces a new failure mode when claudeHookSurfaceIsListed has transient false negatives — and there's no observability (no log, no metric) to distinguish a correctly-blocked stale surface from a spurious socket-read failure.

Comment on lines 309 to 314
fi
;;
*)
return 1
;;
esac
index=$((index + 1))

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 Removing return 1 in the *) case allows scanning past positional args — correct fix, but a gap in test coverage

The removal of the early return 1 from the positional-arg case in both extract_claude_resume_session_id and claude_explicit_session_flag_present is the correct fix for the "prompt before --resume" scenario. The existing --resume) handler's index + 1 < ${#args[@]} guard prevents extracting the wrong arg when --resume is the last token. However, there is no test for ["--resume", "session-id", "some-extra-positional"] to confirm that a positional after a valid --resume <id> pair does not interfere. Adding this case alongside the new test_live_socket_resume_after_prompt_text_does_not_inject_session_id would lock in both halves of the ordering invariant.

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!

austinywang and others added 4 commits June 19, 2026 15:44
The #5989 merge added 7 lines to CLI/cmux.swift; regenerate the budget
so the length check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@austinywang
austinywang merged commit d886638 into main Jun 19, 2026
26 checks passed

This branch was successfully deployed

1 active deployment
Preview – cmux — 9ff87f1d 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.

2 participants