From 5d6ceb74742d8ffe331ec9e9845f55f5711ff540 Mon Sep 17 00:00:00 2001 From: Steve Date: Thu, 13 Aug 2026 10:27:49 -0700 Subject: [PATCH 1/2] Add failing regression test: hidden claude attach subcommand gets hook injection `claude attach ` (the attach door for --bg background sessions) is a real subcommand but hidden from `claude --help`, so it's missing from the wrapper's builtin-command list. The wrapper classifies it as a session entrypoint and injects --session-id/--settings ahead of it, which makes the real CLI treat "attach" as the [prompt] positional: instead of attaching, it mints a brand-new session with "attach" pre-filled in the composer. Test-only commit (two-commit regression policy): CI should go red here. Co-Authored-By: Claude Fable 5 --- tests/test_claude_wrapper_hooks.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/tests/test_claude_wrapper_hooks.py b/tests/test_claude_wrapper_hooks.py index b2b66d40baf..88d0a851b9c 100644 --- a/tests/test_claude_wrapper_hooks.py +++ b/tests/test_claude_wrapper_hooks.py @@ -877,6 +877,23 @@ def test_command_like_invocations_bypass_hook_injection(failures: list[str]) -> expect("--session-id" not in real_argv, f"agents after global option passthrough: expected no --session-id injection, got {real_argv}", failures) +def test_hidden_attach_subcommand_bypasses_hook_injection(failures: list[str]) -> None: + # `claude attach ` is a real subcommand (the attach door for `--bg` + # background sessions) but is hidden from `claude --help`, so it's easy to + # miss when refreshing the builtin-command list. Injecting + # --session-id/--settings ahead of it makes the CLI treat "attach" as the + # [prompt] positional and mint a fresh session instead of attaching. + code, real_argv, _, stderr, _, node_options, _, _, _, _ = run_wrapper( + socket_state="live", + argv=["attach", "abc12345"], + ) + expect(code == 0, f"attach passthrough: wrapper exited {code}: {stderr}", failures) + expect(real_argv == ["attach", "abc12345"], f"attach passthrough: expected raw argv, got {real_argv}", failures) + expect("--settings" not in real_argv, f"attach passthrough: expected no --settings injection, got {real_argv}", failures) + expect("--session-id" not in real_argv, f"attach passthrough: expected no --session-id injection, got {real_argv}", failures) + expect(node_options == "__UNSET__", f"attach passthrough: expected no NODE_OPTIONS injection, got {node_options!r}", failures) + + def test_passthrough_flags_bypass_hook_injection(failures: list[str]) -> None: for flag in ("--help", "--version", "-h", "-v"): code, real_argv, _, stderr, _, node_options, _, _, _, _ = run_wrapper( @@ -1893,6 +1910,7 @@ def main() -> int: test_live_socket_empty_settings_warns_instead_of_silent_drop(failures) test_plain_claude_launch_argv_has_no_empty_argument(failures) test_command_like_invocations_bypass_hook_injection(failures) + test_hidden_attach_subcommand_bypasses_hook_injection(failures) test_passthrough_flags_bypass_hook_injection(failures) test_agents_subcommand_removes_cmux_terminal_fingerprint(failures) test_hooks_disabled_preserves_cmux_terminal_env_for_custom_hooks(failures) From 97cbc207730e9451c986853d02d4c50204033730 Mon Sep 17 00:00:00 2001 From: Steve Date: Thu, 13 Aug 2026 10:28:47 -0700 Subject: [PATCH 2/2] Pass the hidden claude attach subcommand through without hook injection Add `attach` to claude_builtin_command_name so the wrapper classifies `claude attach ` as a command invocation and execs the real CLI with argv untouched, like agents/mcp/etc. Hook settings are meaningless for attach anyway: hooks live in the already-running background writer process, and the injected fresh --session-id is exactly what turned an attach into a new session. Co-Authored-By: Claude Fable 5 --- Resources/bin/cmux-claude-wrapper | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Resources/bin/cmux-claude-wrapper b/Resources/bin/cmux-claude-wrapper index 1cd5d09487b..b292ca35034 100755 --- a/Resources/bin/cmux-claude-wrapper +++ b/Resources/bin/cmux-claude-wrapper @@ -772,7 +772,7 @@ claude_passthrough_option_flag() { claude_builtin_command_name() { case "$1" in - agents|auth|auto-mode|config|api-key|daemon|doctor|install|mcp|\ + agents|attach|auth|auto-mode|config|api-key|daemon|doctor|install|mcp|\ experimental-next|plugin|plugins|project|rc|remote-control|setup-token|\ ultrareview|update|upgrade) return 0