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
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
//! Tests for [`super`] — the managed-spawn env scrub (issue #4467).
//!
//! Why: the scrub is a silent-failure guard, so its own tests have to be able to
//! fail. The two directions that matter are asserted independently: the
//! transcript-suppressing marker must be scrubbed (under-scrub → managed
//! sessions lose transcripts), and `CLAUDE_CONFIG_DIR` must NOT be scrubbed
//! (over-scrub → issue #4451 returns and every delegation degrades to
//! `general-purpose`).
use super::*;
/// Every marker, by LITERAL name.
///
/// Why (review follow-up on #4467): every other test in this file iterates
/// `INHERITED_SESSION_MARKERS`, so deleting an entry made them all pass
/// vacuously — `CLAUDE_PID` and `CLAUDE_EFFORT` in particular were pinned by
/// nothing, and removing either kept the whole suite green. Restating the names
/// here is the point: this list and the production constant must be edited
/// together, deliberately.
const EXPECTED_MARKERS: &[&str] = &[
"CLAUDE_CODE_CHILD_SESSION",
"CLAUDE_CODE_SESSION_ID",
"CLAUDECODE",
"CLAUDE_PID",
"CLAUDE_EFFORT",
"CLAUDE_CODE_EXECPATH",
];
/// The single most important assertion in this file: the variable that
/// disables transcript saving is actually on the scrub list (issue #4467).
#[test]
fn suppressing_marker_is_scrubbed() {
assert_eq!(
TRANSCRIPT_SUPPRESSING_MARKER, "CLAUDE_CODE_CHILD_SESSION",
"the suppressing marker's identity is load-bearing; it is the sole trigger \
in Claude Code's persistence gate"
);
assert!(
INHERITED_SESSION_MARKERS.contains(&TRANSCRIPT_SUPPRESSING_MARKER),
"the marker that disables transcript saving must be scrubbed; \
without it managed sessions get no --resume/--continue/rewind recovery"
);
}
/// Pins the full marker set by literal name, in order, so deleting ANY entry
/// fails — including `CLAUDE_PID` and `CLAUDE_EFFORT`, which previously had no
/// literal assertion anywhere in the suite.
#[test]
fn every_marker_is_pinned_by_literal_name() {
assert_eq!(
INHERITED_SESSION_MARKERS, EXPECTED_MARKERS,
"the scrub list changed. If that is intentional, update EXPECTED_MARKERS in \
this file too — and re-check the kept/scrubbed rationale in the module doc, \
since every entry there has a stated reason"
);
}
/// Over-scrub guard: the deliberate spawn env and the scrub list must be
/// disjoint. Scrubbing `CLAUDE_CONFIG_DIR` re-breaks #4451 (#4455 relies on it).
#[test]
fn marker_list_is_free_of_deliberate_spawn_env() {
for deliberate in DELIBERATE_SPAWN_ENV {
assert!(
!scrubbed_on_spawn().any(|name| name == *deliberate),
"{deliberate} is set deliberately by a managed spawn and must never \
be scrubbed — scrubbing CLAUDE_CONFIG_DIR re-breaks #4451"
);
}
}
/// `CLAUDE_CONFIG_DIR` named explicitly, not just via the loop above, so the
/// guard survives someone emptying `DELIBERATE_SPAWN_ENV` (which would make
/// `marker_list_is_free_of_deliberate_spawn_env` pass vacuously).
#[test]
fn config_dir_is_never_scrubbed() {
assert!(
!INHERITED_SESSION_MARKERS.contains(&"CLAUDE_CONFIG_DIR"),
"CLAUDE_CONFIG_DIR relocation is what puts the bundled roster in the \
`user` settings tier a managed session loads (#4455); scrubbing it \
silently restores the #4451 zero-specialists failure"
);
assert!(
DELIBERATE_SPAWN_ENV.contains(&"CLAUDE_CONFIG_DIR"),
"CLAUDE_CONFIG_DIR must stay listed as deliberate spawn env"
);
}
/// A duplicate would emit a redundant `-u` flag on every managed pane command.
#[test]
fn marker_list_has_no_duplicates() {
let mut seen: Vec<&str> = Vec::new();
for name in INHERITED_SESSION_MARKERS {
assert!(
!seen.contains(name),
"{name} appears twice in INHERITED_SESSION_MARKERS"
);
seen.push(name);
}
}
/// Every marker must reach the `env` prefix — a marker on the list but missing
/// from the flags would be scrubbed on the `Command` paths only.
#[test]
fn env_unset_flags_covers_every_marker() {
let flags = env_unset_flags();
for name in INHERITED_SESSION_MARKERS {
assert!(
flags.contains(&format!(" -u {name}")),
"env_unset_flags() must unset {name}; got {flags:?}"
);
}
// #8453: the markers, then tm's own stamps.
assert_eq!(
parse_env_unset_vars(&flags),
scrubbed_on_spawn().collect::<Vec<_>>(),
"the flags must round-trip back to exactly the scrub list"
);
}
/// The flags concatenate onto an existing `env -u ANTHROPIC_API_KEY` prefix, so
/// each needs a LEADING space; a missing one would fuse two tokens together and
/// break every managed spawn.
#[test]
fn env_unset_flags_are_space_prefixed() {
let flags = env_unset_flags();
assert!(
flags.starts_with(" -u "),
"flags must start with a leading space: {flags:?}"
);
assert!(
!flags.contains(" "),
"no double spaces (would survive but signals a formatting bug): {flags:?}"
);
}
/// `scrub_command` must surface every marker as an explicit removal, which
/// `get_envs()` reports as `(key, None)`.
#[test]
fn scrub_command_removes_every_marker() {
let mut cmd = std::process::Command::new("claude");
scrub_command(&mut cmd);
let removed: Vec<String> = cmd
.get_envs()
.filter(|(_, v)| v.is_none())
.map(|(k, _)| k.to_string_lossy().into_owned())
.collect();
for name in INHERITED_SESSION_MARKERS {
assert!(
removed.contains(&(*name).to_owned()),
"scrub_command must env_remove({name}); removed={removed:?}"
);
}
}
/// Over-scrub guard on the `Command` path: an explicit assignment made before
/// the scrub must survive it, because `build_inplace_exec_command` sets
/// `CLAUDE_CONFIG_DIR` and the OAuth token on the same command.
#[test]
fn scrub_command_leaves_deliberate_env_intact() {
let mut cmd = std::process::Command::new("claude");
cmd.env("CLAUDE_CONFIG_DIR", "/tm/config");
cmd.env(crate::core::oauth_token::OAUTH_TOKEN_ENV_VAR, "tok");
scrub_command(&mut cmd);
let kept: Vec<(String, Option<String>)> = cmd
.get_envs()
.map(|(k, v)| {
(
k.to_string_lossy().into_owned(),
v.map(|v| v.to_string_lossy().into_owned()),
)
})
.collect();
assert!(
kept.contains(&(
"CLAUDE_CONFIG_DIR".to_owned(),
Some("/tm/config".to_owned())
)),
"the deliberate CLAUDE_CONFIG_DIR assignment must survive the scrub: {kept:?}"
);
assert!(
kept.contains(&(
crate::core::oauth_token::OAUTH_TOKEN_ENV_VAR.to_owned(),
Some("tok".to_owned())
)),
"the deliberate OAuth token assignment must survive the scrub: {kept:?}"
);
}
/// The parser is what makes the doctor probe non-tautological, so it is tested
/// against a realistic prefix shape rather than only its own output.
#[test]
fn parse_env_unset_vars_reads_the_real_spawn_prefix() {
let prefix = format!(
"env -u ANTHROPIC_API_KEY{} CLAUDE_CONFIG_DIR='/tm/config' /abs/claude",
env_unset_flags()
);
let unset = parse_env_unset_vars(&prefix);
assert_eq!(
unset.first(),
Some(&"ANTHROPIC_API_KEY"),
"the pre-existing API-key scrub must still be detected: {unset:?}"
);
assert!(
unset.contains(&TRANSCRIPT_SUPPRESSING_MARKER),
"the suppressing marker must be visible in the parsed prefix: {unset:?}"
);
}
/// POSIX option termination: the first `NAME=VALUE` ends option parsing, so a
/// later `-u` is NOT an unset — `env` would try to exec `-u` as a command.
///
/// Why this matters (review follow-up on #4467): the earlier parser reported
/// `FOO` unset here, which meant the doctor probe would have passed on an
/// ordering that kills every spawn with `env: -u: No such file or directory`
/// (exit 127). Reporting nothing is the correct answer — the prefix is broken.
#[test]
fn parse_env_unset_vars_stops_at_the_first_assignment() {
let unset = parse_env_unset_vars("env CLAUDE_CODE_CHILD_SESSION=1 -u FOO /abs/claude");
assert_eq!(
unset,
Vec::<&str>::new(),
"an assignment ends option parsing, so no later -u counts as an unset: {unset:?}"
);
// A well-formed prefix — flags BEFORE the assignment — still parses fully.
let good = parse_env_unset_vars("env -u FOO CLAUDE_CONFIG_DIR=/tmp/x /abs/claude");
assert_eq!(
good,
vec!["FOO"],
"flags preceding the assignment must still be collected: {good:?}"
);
}
/// The COMMAND word ends `env`'s argument list, so flags belonging to the
/// command are never mistaken for `env -u` operands.
///
/// Why this matters (review round 2 LOW): `model_inject::build_claude_command`
/// emits NO `NAME=VALUE` assignments, so the assignment stop never fires on its
/// output. Without a COMMAND stop the scan runs to the end of the line, and a
/// future `claude` flag pair spelled `-u <value>` would be counted as an unset
/// variable — inflating what the `tm doctor` probe believes is scrubbed.
#[test]
fn parse_env_unset_vars_stops_at_the_command_word() {
let unset = parse_env_unset_vars("env -u REAL claude --some-flag -u NOT_AN_UNSET");
assert_eq!(
unset,
vec!["REAL"],
"only operands before the command word are unsets: {unset:?}"
);
// The daemon shape: the command word can be an absolute path, and the
// disclaim wrapper puts a quoted tm path there. Neither may be scanned past.
let wrapped =
parse_env_unset_vars("env -u REAL '/w/tm' internal-spawn-disclaimed /abs/claude -u NOPE");
assert_eq!(
wrapped,
vec!["REAL"],
"the wrapper path is a command word, not an operand: {wrapped:?}"
);
}
/// A malformed trailing `-u` must not panic.
#[test]
fn parse_env_unset_vars_tolerates_trailing_flag() {
assert_eq!(parse_env_unset_vars("env -u"), Vec::<&str>::new());
}
#[test]
fn markers_present_in_detects_a_set_marker() {
let present = markers_present_in(|name| name == TRANSCRIPT_SUPPRESSING_MARKER);
assert_eq!(present, vec![TRANSCRIPT_SUPPRESSING_MARKER]);
}
#[test]
fn markers_present_in_is_empty_for_a_clean_env() {
assert!(markers_present_in(|_| false).is_empty());
}
#[test]
fn markers_present_in_reports_every_marker_when_all_are_set() {
assert_eq!(
markers_present_in(|_| true),
INHERITED_SESSION_MARKERS.to_vec()
);
}
/// #8453: the profile stamp, by LITERAL name, is cleared by both helpers —
/// the string prefix every shell launch line carries and the `Command` scrub.
/// This is the choke point: a launch path that routes through either helper
/// cannot pass a supervisor's stamp on to its child.
#[test]
fn both_helpers_clear_the_profile_stamp() {
let stamp = "TRUSTY_MPM_SESSION_PROFILE";
assert!(
parse_env_unset_vars(&format!("env{} x", env_unset_flags())).contains(&stamp),
"env_unset_flags must unset {stamp}"
);
let mut cmd = std::process::Command::new("true");
scrub_command(&mut cmd);
assert!(
cmd.get_envs().any(|(k, v)| k == stamp && v.is_none()),
"scrub_command must remove {stamp}"
);
}
/// #8453: the prefix really drops an inherited stamp through the system
/// `env`. The parent is given `TRUSTY_MPM_SESSION_PROFILE=supervisor` on its
/// own `Command` (no process-global write) and the child prints the variable.
#[test]
fn the_env_prefix_really_drops_an_inherited_profile_stamp() {
let out = std::process::Command::new("sh")
.arg("-c")
.arg(format!(
"env{} sh -c 'printf %s \"${{TRUSTY_MPM_SESSION_PROFILE-absent}}\"'",
env_unset_flags()
))
.env("TRUSTY_MPM_SESSION_PROFILE", "supervisor")
.output()
.expect("run sh");
assert!(out.status.success(), "{out:?}");
assert_eq!(String::from_utf8_lossy(&out.stdout), "absent");
}
/// #8583: `RUSTUP_TOOLCHAIN`, by LITERAL name, is cleared by both helpers, so
/// no launch path hands a session the toolchain `tm`'s own directory resolved.
#[test]
fn both_helpers_clear_the_toolchain_override() {
let var = "RUSTUP_TOOLCHAIN";
assert!(
parse_env_unset_vars(&format!("env{} x", env_unset_flags())).contains(&var),
"env_unset_flags must unset {var}"
);
let mut cmd = std::process::Command::new("true");
scrub_command(&mut cmd);
assert!(
cmd.get_envs().any(|(k, v)| k == var && v.is_none()),
"scrub_command must remove {var}"
);
}
/// #8583: the prefix really drops an inherited `RUSTUP_TOOLCHAIN` through the
/// system `env`, so rustup falls through to the project's `rust-toolchain.toml`.
/// The parent is given the issue's observed `1.94.1` on its own `Command`.
#[test]
fn the_env_prefix_really_drops_an_inherited_toolchain_override() {
let out = std::process::Command::new("sh")
.arg("-c")
.arg(format!(
"env{} sh -c 'printf %s \"${{RUSTUP_TOOLCHAIN-absent}}\"'",
env_unset_flags()
))
.env("RUSTUP_TOOLCHAIN", "1.94.1")
.output()
.expect("run sh");
assert!(out.status.success(), "{out:?}");
assert_eq!(String::from_utf8_lossy(&out.stdout), "absent");
}