1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
//! Disclaim the pane-launched `claude` off the shared tmux server (issue #2997).
//!
//! Why: unlike the three spawn shapes in [`super`] (capture/status/piped),
//! trusty-mpm's DEFAULT managed session does not `posix_spawn` `claude`
//! directly — it types a shell command into a tmux pane via `send-keys`, and
//! the pane's shell (forked by the ONE long-lived, shared tmux server) execs
//! `claude`. So the disclaim attribute the parent would set at `posix_spawn`
//! never applies to that `claude`: tccd walks its responsibility chain
//! `claude → zsh → tmux server` and blames the shared server. #3037 already
//! disclaims the `tmux new-session` CLIENT ([`crate::core::tmux::run_tmux_with_bin`]),
//! which moved attribution off the signed `trusty-mpm` binary and ONTO the
//! tmux server — but a `RemovableVolumes`/App-Data prompt still surfaces,
//! now subject-lined "tmux", because the server is still SOME process's
//! responsible ancestor for every pane (issue #2997, 2026-07-19 evidence).
//! What: [`disclaim_pane_command`] rewrites the pane's `claude` invocation to
//! route it through the running `tm`/`trusty-mpm` binary's hidden
//! [`PANE_DISCLAIM_SUBCOMMAND`], which `posix_spawn`s `claude` WITH the
//! disclaim attribute set (reusing [`super::disclaimed_status`], the #3037
//! inherited-stdio shape). That makes `claude` its OWN responsible process, so
//! tccd stops the walk at `claude` (a stably-signed `com.anthropic.claude-code`
//! identity) instead of the tmux server. On non-macOS, or when
//! [`super::DISABLE_ENV`] is set, the invocation is returned unchanged.
//! Test: `wraps_invocation_when_wrapper_present`,
//! `passes_through_when_no_wrapper`, `single_quotes_wrapper_with_space`,
//! `preserves_flags_after_program`,
//! `wraps_the_scrubbed_launch_line_with_env_as_the_program` (all
//! cross-platform, pure).
/// The hidden `tm`/`trusty-mpm` subcommand a managed pane invokes to launch
/// its `claude` disclaimed (issue #2997).
///
/// Why: the pane cannot set a `posix_spawn` attribute itself — it can only run
/// a shell command. Routing `claude` through this subcommand puts a tm-owned
/// process between the pane shell and `claude` that DOES set the disclaim
/// attribute. Both `[[bin]]` targets (`tm` and `trusty-mpm`) build from the
/// same `main.rs`, so whichever binary built the pane command can service it.
/// What: the literal subcommand name; the argv that follows is `<program>
/// <args...>`, spawned via [`super::disclaimed_status`].
/// Test: `wraps_invocation_when_wrapper_present`.
pub const PANE_DISCLAIM_SUBCOMMAND: &str = "internal-spawn-disclaimed";
/// POSIX single-quote `s` so a path with a space survives the pane shell's
/// word-splitting intact.
///
/// Why: `current_exe()` under a home like `/Users/John Doe/.cargo/bin/tm`
/// would otherwise word-split and break the pane launch — the same failure
/// [`crate::runtime::claude_code`]'s `shell_single_quote` guards against.
/// What: wraps `s` in single quotes, escaping any embedded `'` with the
/// canonical `'\''` close-reopen sequence.
/// Test: `single_quotes_wrapper_with_space`.
/// Build the pane command that launches `claude_invocation` disclaimed, given
/// the wrapper binary to route through (pure — the seam that makes the
/// wrapping unit-testable without a real `current_exe`/OS).
///
/// Why: separating the string construction from the [`current_wrapper_bin`]
/// resolution lets a hermetic test assert both the wrapped shape (with a fake
/// wrapper) and the pass-through (with `None`) deterministically on any
/// platform.
/// What: `Some(bin)` → `<'bin'> <PANE_DISCLAIM_SUBCOMMAND> <claude_invocation>`
/// (the wrapper path single-quoted, the invocation appended verbatim so its
/// existing flags follow the program token as separate shell words). `None`
/// → `claude_invocation` unchanged.
/// Test: `wraps_invocation_when_wrapper_present`,
/// `passes_through_when_no_wrapper`, `preserves_flags_after_program`,
/// `wraps_the_scrubbed_launch_line_with_env_as_the_program`.
/// Resolve the wrapper binary (the running `tm`/`trusty-mpm` executable) when
/// pane disclaim is active, else `None`.
///
/// Why: the wrapper must be a binary that services [`PANE_DISCLAIM_SUBCOMMAND`]
/// — that is exactly the process building the pane command, so `current_exe()`
/// is always the right absolute path (and absolute matters: the pane inherits
/// the daemon's minimal launchd `PATH`, so a bare name could fail to resolve —
/// the #1298 rationale for the resolved `claude_bin`).
/// What: `Some(path)` on macOS when [`super::DISABLE_ENV`] is unset and
/// `current_exe()` resolves to valid UTF-8; `None` on every other OS, when the
/// escape hatch is set, or when the path cannot be read/encoded (in which case
/// the caller falls back to launching `claude` directly — no regression).
/// Test: exercised end-to-end by the live managed-session path; the pure
/// wrapping logic is covered via [`disclaim_pane_command_with`].
/// Rewrite a pane-launched `claude` invocation so the pane's `claude` child is
/// spawned disclaimed (its own TCC responsible process) instead of inheriting
/// the shared tmux server's identity (issue #2997).
///
/// Why: see the module docs — every managed session types this command into a
/// tmux pane, and without the wrapper tccd attributes the pane's `claude`
/// `RemovableVolumes`/App-Data access up to the shared tmux server.
/// What: on macOS (and with [`super::DISABLE_ENV`] unset) prefixes
/// `claude_invocation` with `<current_exe> <PANE_DISCLAIM_SUBCOMMAND>`; on any
/// other platform, or with the escape hatch set, returns it unchanged.
/// `claude_invocation` may be a bare program token (the daemon's resolved
/// `claude_bin`, with its flags appended downstream) or a full command string
/// (the CLI paths, which since #4467 lead with `env -u <marker…> claude …`) —
/// prefixing works identically for both, and in the `env` case the shim spawns
/// `env` disclaimed and `env` execs `claude` in that same process.
/// Test: the pure shape is covered by [`disclaim_pane_command_with`]'s tests;
/// the resolution is exercised by the live managed-session launch path.