Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
58 commits
Select commit Hold shift + click to select a range
4714974
Add regression for Claude restore cwd drift
austinywang Jun 16, 2026
5eb3a18
Restore Claude agent hooks from launch cwd
austinywang Jun 16, 2026
9993961
Merge remote-tracking branch 'origin/main' into issue-6194-session-re…
austinywang Jun 16, 2026
7637f8b
fix: prefer claude hook tty surface for resume binding
austinywang Jun 18, 2026
711e6c7
merge: sync with main
austinywang Jun 18, 2026
ec5151f
fix: keep non-agent resume bindings unchanged when retargeting
austinywang Jun 18, 2026
a0c1d71
chore: refresh swift file length budget
austinywang Jun 18, 2026
35a26a9
fix: keep invalid explicit claude surface from falling through
austinywang Jun 18, 2026
47df7bc
merge: sync with main
austinywang Jun 18, 2026
80250aa
fix: document restore cwd snapshot fallback
austinywang Jun 18, 2026
f08e50f
test: cover mapped claude surface priority
austinywang Jun 18, 2026
b05b804
fix: keep mapped claude session surface authoritative
austinywang Jun 18, 2026
ce73aba
test: move resume coverage to Swift Testing
austinywang Jun 18, 2026
1e97a08
Merge remote-tracking branch 'origin/main' into issue-6194-session-re…
austinywang Jun 18, 2026
26e22ee
test: separate ambient claude hook surface
austinywang Jun 18, 2026
9d43d43
test: keep claude hook session fixture current
austinywang Jun 18, 2026
4202fb4
merge: sync with main
austinywang Jun 18, 2026
ea66e85
merge: sync with main
austinywang Jun 18, 2026
dd55181
merge: sync with main
austinywang Jun 19, 2026
d9c2102
test: cover Claude hook leaked workspace routing
austinywang Jun 19, 2026
0e840de
fix: prefer Claude hook TTY routing before leaked env
austinywang Jun 19, 2026
053b5ab
fix: return Claude hook test server semaphore
austinywang Jun 19, 2026
0f93c53
Merge branch 'main' into issue-6194-session-restore-resume-cwd
lawrencecchen Jun 19, 2026
f1e104b
fix: harden Claude hook restore routing
austinywang Jun 19, 2026
de2f5a4
fix: localize Claude hook surface errors
austinywang Jun 19, 2026
d0b9577
fix: initialize shortcut test state before skip
austinywang Jun 19, 2026
b86b4e1
Merge remote-tracking branch 'origin/main' into issue-6194-session-re…
austinywang Jun 19, 2026
e9efdad
test: rely on main shortcut skip handling
austinywang Jun 19, 2026
a416381
Merge remote-tracking branch 'origin/main' into issue-6194-session-re…
austinywang Jun 19, 2026
1adba51
test: cover stale Claude TTY workspace binding
austinywang Jun 19, 2026
2e2a07e
fix: ignore stale Claude TTY workspace bindings
austinywang Jun 19, 2026
16a7b2e
Merge origin/main into issue-6194-session-restore-resume-cwd
austinywang Jun 19, 2026
c730a0a
test: cover zsh claude wrapper after user function override
austinywang Jun 19, 2026
a8c26d8
fix: restore Claude wrapper after zsh startup overrides
austinywang Jun 19, 2026
17ad80c
fix: prefer Claude process binding for hook routing
austinywang Jun 19, 2026
9645718
fix: isolate Claude hook process probe
austinywang Jun 19, 2026
78d09b3
fix: connect Claude PID probe socket
austinywang Jun 19, 2026
43f1c4b
fix: authenticate Claude PID probe socket
austinywang Jun 19, 2026
4147f96
test: cover Claude resume self-heal for mismatched CLAUDE_CONFIG_DIR
austinywang Jun 19, 2026
7a99666
fix: self-heal CLAUDE_CONFIG_DIR on Claude resume
austinywang Jun 19, 2026
52f5952
test: cover Claude prompt resume text parsing
austinywang Jun 19, 2026
7741fb0
fix: stop Claude resume parsing at prompt text
austinywang Jun 19, 2026
4fad54b
Merge remote-tracking branch 'origin/main' into issue-6194-session-re…
austinywang Jun 19, 2026
e807dde
fix: harden Claude resume restore targeting
austinywang Jun 19, 2026
6eca863
fix: define Claude resume parser helper before passthrough
austinywang Jun 19, 2026
e8928e8
fix: tolerate Claude value flags before resume
austinywang Jun 19, 2026
79df83f
test: cover Claude resume auth selection
austinywang Jun 19, 2026
31fcd98
fix: preserve Claude resume auth selection
austinywang Jun 19, 2026
22e3939
fix: constrain Claude resume self-heal
austinywang Jun 19, 2026
ac299ea
fix: bound Claude resume resolution
austinywang Jun 19, 2026
397248a
fix: harden Claude resume wrapper resolution
austinywang Jun 19, 2026
8aa4f89
fix: route Claude hooks past stale tty bindings
austinywang Jun 19, 2026
bfe42d8
test: keep Claude resume expectations out of XCTest diff
austinywang Jun 19, 2026
1780d27
Merge remote-tracking branch 'origin/main' into issue-6194-session-re…
austinywang Jun 19, 2026
6bf5d56
Add stale Claude shell wrapper regression
austinywang Jun 19, 2026
1cc03e1
Route Claude shell functions through shim resolver
austinywang Jun 19, 2026
c707f64
Merge remote-tracking branch 'origin/main' into issue-6194-session-re…
austinywang Jun 19, 2026
9ff87f1
chore: refresh Swift file length budget after origin/main merge
austinywang Jun 19, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 6 additions & 4 deletions .github/swift-file-length-budget.tsv
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
# cmux-owned Swift file length budget.
# Format: max_lines<TAB>relative path
# Reduce counts as files shrink. CI fails if tracked files exceed this budget.
34323 CLI/cmux.swift
34344 CLI/cmux.swift
17606 Sources/AppDelegate.swift
16021 Sources/ContentView.swift
14100 Sources/TerminalController.swift
12695 Sources/Workspace.swift
12144 cmuxTests/AppDelegateShortcutRoutingTests.swift
11841 Sources/GhosttyTerminalView.swift
11411 Sources/Panels/BrowserPanel.swift
9339 cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
9335 cmuxTests/CLINotifyProcessIntegrationRegressionTests.swift
7988 Sources/Panels/BrowserPanelView.swift
7355 cmuxTests/WorkspaceUnitTests.swift
7218 cmuxTests/WorkspaceRemoteConnectionTests.swift
Expand Down Expand Up @@ -47,8 +47,8 @@
2126 cmuxTests/CmuxConfigTests.swift
2105 Sources/SessionPersistence.swift
2091 cmuxTests/ShortcutAndCommandPaletteTests.swift
1954 Sources/RestorableAgentSession.swift
1949 Sources/Panels/BrowserWebAuthnSupport.swift
1945 Sources/RestorableAgentSession.swift
1940 Sources/KeyboardShortcutSettingsFileStore.swift
1869 cmuxTests/NotificationAndMenuBarTests.swift
1810 Sources/SessionIndexStore.swift
Expand Down Expand Up @@ -101,6 +101,7 @@
899 Sources/Panels/MarkdownWebRenderer.swift
885 Sources/Panels/TerminalPanel.swift
877 Packages/Shared/CmuxAgentChat/Tests/CmuxAgentChatTests/ChatConversationStoreTests.swift
871 cmuxTests/ClaudeHookSurfaceResolutionSwiftTests.swift
868 Sources/Panels/BrowserScreenshotSnapshotter.swift
865 Packages/iOS/CmuxMobileShellUI/Sources/CmuxMobileShellUI/WorkspaceDetailView.swift
859 Packages/macOS/CmuxControlSocket/Sources/CmuxControlSocket/Coordinator/Workspace/ControlCommandCoordinator+Workspace.swift
Expand All @@ -111,7 +112,6 @@
810 Packages/macOS/CmuxSwiftRender/Tests/CmuxSwiftRenderTests/SwiftViewInterpreterTests.swift
802 Sources/WorkspaceContentView.swift
797 Sources/ClosedItemHistory.swift
787 cmuxTests/ClaudeHookSurfaceResolutionSwiftTests.swift
779 cmuxUITests/BrowserOmnibarSuggestionsUITests.swift
774 cmuxUITests/BrowserFixtureInteractionUITests.swift
772 Sources/MainWindowFocusController.swift
Expand Down Expand Up @@ -173,6 +173,7 @@
586 Packages/macOS/CmuxRemoteSession/Sources/CmuxRemoteSession/Session/RemoteSessionCoordinator+PortScan.swift
586 Sources/JSONCParser.swift
585 Sources/Cloud/VMClient.swift
583 cmuxTests/GhosttyTerminalStartupEnvironmentTests.swift
580 Packages/macOS/CmuxExtensionKit/Tests/CmuxExtensionKitTests/CmuxExtensionKitTests.swift
580 cmuxTests/CLIHookNoResponseTests.swift
577 cmuxTests/AppearanceSettingsTests.swift
Expand Down Expand Up @@ -211,6 +212,7 @@
518 Packages/macOS/CmuxSwiftRender/Tests/CmuxSwiftRenderTests/Corpus/stress-git-review-queue-command-deck.swift
517 Sources/PaneMemoryGuardrail.swift
516 Sources/TerminalImageTransfer.swift
515 Packages/macOS/CmuxTerminal/Sources/CmuxTerminal/Surface/TerminalSurface+Sizing.swift
514 Packages/macOS/CmuxSwiftRender/Sources/CmuxSwiftRender/ExpressionEvaluator.swift
514 cmuxUITests/UpdatePillUITests.swift
509 Packages/macOS/CMUXAgentLaunch/Sources/CMUXAgentLaunch/AgentLaunchSanitizerAdditionalPolicies.swift
Expand Down
24 changes: 19 additions & 5 deletions CLI/cmux.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22921,14 +22921,28 @@ struct CMUXCLI {
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
Comment on lines 22921 to 22948

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.

) {
callerTTYBindingCache = processBinding
}
}
}
return callerTTYBindingCache
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -120,9 +120,43 @@ extension TerminalSurface {
try fileManager.createDirectory(at: shimDirectory, withIntermediateDirectories: true)
let script = """
#!/usr/bin/env bash
cmux_wrapper=\(shellSingleQuoted(wrapperURL.path))
if [[ ! -x "$cmux_wrapper" && -n "${CMUX_BUNDLED_CLI_PATH:-}" ]]; then
cmux_candidate="$(dirname "$CMUX_BUNDLED_CLI_PATH")/cmux-claude-wrapper"
if [[ -x "$cmux_candidate" ]]; then
cmux_wrapper="$cmux_candidate"
fi
fi
if [[ ! -x "$cmux_wrapper" ]]; then
cmux_cli="$(command -v cmux 2>/dev/null || true)"
if [[ -n "$cmux_cli" ]]; then
cmux_candidate="$(dirname "$cmux_cli")/cmux-claude-wrapper"
if [[ -x "$cmux_candidate" ]]; then
cmux_wrapper="$cmux_candidate"
fi
fi
fi
export CMUX_CLAUDE_WRAPPER_SHIM=\(shellSingleQuoted(shimURL.path))
export CMUX_CLAUDE_WRAPPER_SHIM_ROOT=\(shellSingleQuoted(shimDirectory.path))
exec \(shellSingleQuoted(wrapperURL.path)) "$@"
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 "$@"
Comment on lines +141 to +159

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.

"""
try script.write(to: shimURL, atomically: true, encoding: .utf8)
try fileManager.setAttributes([.posixPermissions: 0o700], ofItemAtPath: shimURL.path)
Expand Down
5 changes: 3 additions & 2 deletions Resources/bin/cmux-claude-wrapper
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ cmux_claude_wrapper_is_self_or_shim() {
return 0
fi
case "$candidate" in
*/cmux-cli-shims/*/claude|*/cmux-cli-shims/claude)
return 0
;;
*/Contents/Resources/bin/claude|*/Resources/bin/claude)
return 0
;;
Expand Down Expand Up @@ -306,7 +309,6 @@ extract_claude_resume_session_id() {
fi
;;
*)
return 1
;;
esac
index=$((index + 1))
Comment on lines 309 to 314

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!

Expand Down Expand Up @@ -337,7 +339,6 @@ claude_explicit_session_flag_present() {
fi
;;
*)
return 1
;;
esac
index=$((index + 1))
Expand Down
53 changes: 51 additions & 2 deletions Resources/shell-integration/cmux-bash-integration.bash
Original file line number Diff line number Diff line change
Expand Up @@ -228,10 +228,46 @@ _cmux_install_cli_command_shim() {
{
printf '%s\n' '#!/usr/bin/env bash'
if [[ "$command_name" == "claude" ]]; then
printf 'cmux_wrapper="%s"\n' "$escaped_wrapper"
printf '%s\n' 'if [[ ! -x "$cmux_wrapper" && -n "${CMUX_BUNDLED_CLI_PATH:-}" ]]; then'
printf '%s\n' ' cmux_candidate="$(dirname "$CMUX_BUNDLED_CLI_PATH")/cmux-claude-wrapper"'
printf '%s\n' ' if [[ -x "$cmux_candidate" ]]; then'
printf '%s\n' ' cmux_wrapper="$cmux_candidate"'
printf '%s\n' ' fi'
printf '%s\n' 'fi'
printf '%s\n' 'if [[ ! -x "$cmux_wrapper" ]]; then'
printf '%s\n' ' cmux_cli="$(command -v cmux 2>/dev/null || true)"'
printf '%s\n' ' if [[ -n "$cmux_cli" ]]; then'
printf '%s\n' ' cmux_candidate="$(dirname "$cmux_cli")/cmux-claude-wrapper"'
printf '%s\n' ' if [[ -x "$cmux_candidate" ]]; then'
printf '%s\n' ' cmux_wrapper="$cmux_candidate"'
printf '%s\n' ' fi'
printf '%s\n' ' fi'
printf '%s\n' 'fi'
printf 'export CMUX_CLAUDE_WRAPPER_SHIM="%s"\n' "$shim_path"
printf 'export CMUX_CLAUDE_WRAPPER_SHIM_ROOT="%s"\n' "$shim_root"
printf '%s\n' 'if [[ -x "$cmux_wrapper" ]]; then'
printf '%s\n' ' exec "$cmux_wrapper" "$@"'
printf '%s\n' 'fi'
printf '%s\n' 'cmux_path_without_shim=""'
printf '%s\n' 'cmux_old_ifs="$IFS"'
printf '%s\n' 'IFS=:'
printf '%s\n' 'for cmux_entry in ${PATH:-}; do'
printf '%s\n' ' if [[ "$cmux_entry" == "$CMUX_CLAUDE_WRAPPER_SHIM_ROOT" || "$cmux_entry" == */cmux-cli-shims/* || "$cmux_entry" == */cmux-cli-shims ]]; then'
printf '%s\n' ' continue'
printf '%s\n' ' fi'
printf '%s\n' ' if [[ -z "$cmux_path_without_shim" ]]; then'
printf '%s\n' ' cmux_path_without_shim="$cmux_entry"'
printf '%s\n' ' else'
printf '%s\n' ' cmux_path_without_shim="$cmux_path_without_shim:$cmux_entry"'
printf '%s\n' ' fi'
printf '%s\n' 'done'
printf '%s\n' 'IFS="$cmux_old_ifs"'
printf '%s\n' 'export PATH="$cmux_path_without_shim"'
printf '%s\n' 'exec claude "$@"'
else
printf 'exec "%s" "$@"\n' "$escaped_wrapper"
fi
printf 'exec "%s" "$@"\n' "$escaped_wrapper"
} >"$shim_path" 2>/dev/null || return 0
/bin/chmod 0700 "$shim_path" >/dev/null 2>&1 || return 0

Expand All @@ -243,6 +279,15 @@ _cmux_install_cli_command_shim() {
PATH="$(_cmux_path_prepend_unique_directory "$shim_root" "${PATH-}")"
hash -r >/dev/null 2>&1 || true
}
_cmux_claude_wrapper_command() {
if [[ -x "${CMUX_CLAUDE_WRAPPER_SHIM:-}" ]]; then
"$CMUX_CLAUDE_WRAPPER_SHIM" "$@"
elif [[ -x "${_CMUX_CLAUDE_WRAPPER:-}" ]]; then
"$_CMUX_CLAUDE_WRAPPER" "$@"
else
command claude "$@"
fi
}
_cmux_install_cli_wrapper() {
local command_name="$1"
local wrapper_variable="$2"
Expand Down Expand Up @@ -270,7 +315,11 @@ _cmux_install_cli_wrapper() {
# Keep the bundled wrapper ahead of later PATH mutations. Install it
# via eval so an existing alias cannot break parsing.
unalias "$command_name" >/dev/null 2>&1 || true
eval "$command_name() { \"\${$wrapper_variable}\" \"\$@\"; }"
if [[ "$command_name" == "claude" ]]; then
eval "$command_name() { _cmux_claude_wrapper_command \"\$@\"; }"
else
eval "$command_name() { \"\${$wrapper_variable}\" \"\$@\"; }"
fi
}
_cmux_install_cli_wrapper claude _CMUX_CLAUDE_WRAPPER cmux-claude-wrapper
_cmux_install_cli_wrapper grok _CMUX_GROK_WRAPPER
Expand Down
53 changes: 51 additions & 2 deletions Resources/shell-integration/cmux-zsh-integration.zsh
Original file line number Diff line number Diff line change
Expand Up @@ -235,10 +235,46 @@ _cmux_install_cli_command_shim() {
{
printf '%s\n' '#!/usr/bin/env bash'
if [[ "$command_name" == "claude" ]]; then
printf 'cmux_wrapper="%s"\n' "$escaped_wrapper"
printf '%s\n' 'if [[ ! -x "$cmux_wrapper" && -n "${CMUX_BUNDLED_CLI_PATH:-}" ]]; then'
printf '%s\n' ' cmux_candidate="$(dirname "$CMUX_BUNDLED_CLI_PATH")/cmux-claude-wrapper"'
printf '%s\n' ' if [[ -x "$cmux_candidate" ]]; then'
printf '%s\n' ' cmux_wrapper="$cmux_candidate"'
printf '%s\n' ' fi'
printf '%s\n' 'fi'
printf '%s\n' 'if [[ ! -x "$cmux_wrapper" ]]; then'
printf '%s\n' ' cmux_cli="$(command -v cmux 2>/dev/null || true)"'
printf '%s\n' ' if [[ -n "$cmux_cli" ]]; then'
printf '%s\n' ' cmux_candidate="$(dirname "$cmux_cli")/cmux-claude-wrapper"'
printf '%s\n' ' if [[ -x "$cmux_candidate" ]]; then'
printf '%s\n' ' cmux_wrapper="$cmux_candidate"'
printf '%s\n' ' fi'
printf '%s\n' ' fi'
printf '%s\n' 'fi'
printf 'export CMUX_CLAUDE_WRAPPER_SHIM="%s"\n' "$shim_path"
printf 'export CMUX_CLAUDE_WRAPPER_SHIM_ROOT="%s"\n' "$shim_root"
printf '%s\n' 'if [[ -x "$cmux_wrapper" ]]; then'
printf '%s\n' ' exec "$cmux_wrapper" "$@"'
printf '%s\n' 'fi'
printf '%s\n' 'cmux_path_without_shim=""'
printf '%s\n' 'cmux_old_ifs="$IFS"'
printf '%s\n' 'IFS=:'
printf '%s\n' 'for cmux_entry in ${PATH:-}; do'
printf '%s\n' ' if [[ "$cmux_entry" == "$CMUX_CLAUDE_WRAPPER_SHIM_ROOT" || "$cmux_entry" == */cmux-cli-shims/* || "$cmux_entry" == */cmux-cli-shims ]]; then'
printf '%s\n' ' continue'
printf '%s\n' ' fi'
printf '%s\n' ' if [[ -z "$cmux_path_without_shim" ]]; then'
printf '%s\n' ' cmux_path_without_shim="$cmux_entry"'
printf '%s\n' ' else'
printf '%s\n' ' cmux_path_without_shim="$cmux_path_without_shim:$cmux_entry"'
printf '%s\n' ' fi'
printf '%s\n' 'done'
printf '%s\n' 'IFS="$cmux_old_ifs"'
printf '%s\n' 'export PATH="$cmux_path_without_shim"'
printf '%s\n' 'exec claude "$@"'
else
printf 'exec "%s" "$@"\n' "$escaped_wrapper"
fi
printf 'exec "%s" "$@"\n' "$escaped_wrapper"
} >"$shim_path" 2>/dev/null || return 0
/bin/chmod 0700 "$shim_path" >/dev/null 2>&1 || return 0

Expand All @@ -250,6 +286,15 @@ _cmux_install_cli_command_shim() {
PATH="$(_cmux_path_prepend_unique_directory "$shim_root" "${PATH-}")"
hash -r >/dev/null 2>&1 || rehash >/dev/null 2>&1 || true
}
_cmux_claude_wrapper_command() {
if [[ -x "${CMUX_CLAUDE_WRAPPER_SHIM:-}" ]]; then
"$CMUX_CLAUDE_WRAPPER_SHIM" "$@"
elif [[ -x "${_CMUX_CLAUDE_WRAPPER:-}" ]]; then
"$_CMUX_CLAUDE_WRAPPER" "$@"
else
command claude "$@"
fi
}
_cmux_install_cli_wrapper() {
local command_name="$1"
local wrapper_variable="$2"
Expand All @@ -269,7 +314,11 @@ _cmux_install_cli_wrapper() {
_cmux_install_cli_command_shim "$command_name" "$wrapper_path"
fi
builtin unalias "$command_name" >/dev/null 2>&1 || true
eval "$command_name() { \"\${$wrapper_variable}\" \"\$@\"; }"
if [[ "$command_name" == "claude" ]]; then
eval "$command_name() { _cmux_claude_wrapper_command \"\$@\"; }"
else
eval "$command_name() { \"\${$wrapper_variable}\" \"\$@\"; }"
fi
}
_cmux_install_cli_wrapper claude _CMUX_CLAUDE_WRAPPER cmux-claude-wrapper
_cmux_install_cli_wrapper grok _CMUX_GROK_WRAPPER
Expand Down
46 changes: 44 additions & 2 deletions Resources/shell-integration/fish/config.fish
Original file line number Diff line number Diff line change
Expand Up @@ -144,10 +144,46 @@ if test "$_cmux_integration_enabled" != 0
begin
printf '%s\n' '#!/usr/bin/env bash'
if test "$command_name" = claude
printf 'cmux_wrapper=%s\n' (string escape --style=script -- "$wrapper_path")
printf '%s\n' 'if [[ ! -x "$cmux_wrapper" && -n "${CMUX_BUNDLED_CLI_PATH:-}" ]]; then'
printf '%s\n' ' cmux_candidate="$(dirname "$CMUX_BUNDLED_CLI_PATH")/cmux-claude-wrapper"'
printf '%s\n' ' if [[ -x "$cmux_candidate" ]]; then'
printf '%s\n' ' cmux_wrapper="$cmux_candidate"'
printf '%s\n' ' fi'
printf '%s\n' 'fi'
printf '%s\n' 'if [[ ! -x "$cmux_wrapper" ]]; then'
printf '%s\n' ' cmux_cli="$(command -v cmux 2>/dev/null || true)"'
printf '%s\n' ' if [[ -n "$cmux_cli" ]]; then'
printf '%s\n' ' cmux_candidate="$(dirname "$cmux_cli")/cmux-claude-wrapper"'
printf '%s\n' ' if [[ -x "$cmux_candidate" ]]; then'
printf '%s\n' ' cmux_wrapper="$cmux_candidate"'
printf '%s\n' ' fi'
printf '%s\n' ' fi'
printf '%s\n' 'fi'
printf 'export CMUX_CLAUDE_WRAPPER_SHIM=%s\n' (string escape --style=script -- "$shim_path")
printf 'export CMUX_CLAUDE_WRAPPER_SHIM_ROOT=%s\n' (string escape --style=script -- "$shim_root")
printf '%s\n' 'if [[ -x "$cmux_wrapper" ]]; then'
printf '%s\n' ' exec "$cmux_wrapper" "$@"'
printf '%s\n' 'fi'
printf '%s\n' 'cmux_path_without_shim=""'
printf '%s\n' 'cmux_old_ifs="$IFS"'
printf '%s\n' 'IFS=:'
printf '%s\n' 'for cmux_entry in ${PATH:-}; do'
printf '%s\n' ' if [[ "$cmux_entry" == "$CMUX_CLAUDE_WRAPPER_SHIM_ROOT" || "$cmux_entry" == */cmux-cli-shims/* || "$cmux_entry" == */cmux-cli-shims ]]; then'
printf '%s\n' ' continue'
printf '%s\n' ' fi'
printf '%s\n' ' if [[ -z "$cmux_path_without_shim" ]]; then'
printf '%s\n' ' cmux_path_without_shim="$cmux_entry"'
printf '%s\n' ' else'
printf '%s\n' ' cmux_path_without_shim="$cmux_path_without_shim:$cmux_entry"'
printf '%s\n' ' fi'
printf '%s\n' 'done'
printf '%s\n' 'IFS="$cmux_old_ifs"'
printf '%s\n' 'export PATH="$cmux_path_without_shim"'
printf '%s\n' 'exec claude "$@"'
else
printf 'exec %s "$@"\n' (string escape --style=script -- "$wrapper_path")
end
printf 'exec %s "$@"\n' (string escape --style=script -- "$wrapper_path")
end >"$shim_path" 2>/dev/null; or return 0
chmod 0700 "$shim_path" >/dev/null 2>&1; or return 0
if test "$command_name" = claude
Expand All @@ -171,7 +207,13 @@ if test "$_cmux_integration_enabled" != 0
switch "$command_name"
case claude
function claude --wraps "$wrapper_path" --inherit-variable wrapper_path
"$wrapper_path" $argv
if test -x "$CMUX_CLAUDE_WRAPPER_SHIM"
"$CMUX_CLAUDE_WRAPPER_SHIM" $argv
else if test -x "$wrapper_path"
"$wrapper_path" $argv
else
command claude $argv
end
end
case grok
function grok --wraps "$wrapper_path" --inherit-variable wrapper_path
Expand Down
Loading
Loading