diff --git a/.agents/skills/harness-adapters/SKILL.md b/.agents/skills/harness-adapters/SKILL.md index 85cd11c35ce..e6e10fd0275 100644 --- a/.agents/skills/harness-adapters/SKILL.md +++ b/.agents/skills/harness-adapters/SKILL.md @@ -179,6 +179,8 @@ Claude renders a predicted-next-prompt suggestion as dim/faint text inside an ot A plain `tmux capture-pane` cannot tell that ghost text apart from typed text. Firstmate launches every claude crewmate and secondmate with `CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false`, scoped to firstmate-launched agents through `bin/fm-spawn.sh`, so it never touches the captain's global config. The CLI's `--prompt-suggestions` flag is print/SDK-mode only and does not suppress the interactive composer ghost text, verified empirically on v2.1.186. +The same Claude launch template sanitizes inherited parent Claude session identity so it cannot disable transcript saving for firstmate-launched workers or secondmates. +That sanitize is per-launch and Claude-only; `bin/fm-spawn.sh` owns the exact environment mechanics, with active evidence in `docs/verification/supervision.md`. As defense in depth for any pane that flag cannot reach, including the captain's own firstmate composer that away-mode reads, the shared `fm_composer_strip_ghost` extractor in `bin/fm-composer-lib.sh` removes dim/faint SGR 2 ghost runs before pending-input classification on both ANSI-capable readers (tmux and herdr). Its broader dark-TRUECOLOR placeholder handling and dark-theme tradeoff are documented in `docs/herdr-backend.md` "Composer and injection safety", with active captures in `docs/verification/runtime-backends.md`. That styled capture is internal to the boolean detector only. diff --git a/bin/fm-spawn.sh b/bin/fm-spawn.sh index b27268e1bac..b3f09931c62 100755 --- a/bin/fm-spawn.sh +++ b/bin/fm-spawn.sh @@ -425,7 +425,19 @@ launch_template() { # does NOT suppress the interactive ghost text (verified empirically), so the env # var is the correct control. The dim-aware composer reader in fm-tmux-lib.sh is # the defense-in-depth backstop for any pane this flag cannot reach. - claude) printf '%s' 'CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions __MODELFLAG____EFFORTFLAG__"$(__OPINPUT__ encode launch-brief < __BRIEF__)"' ;; + # + # env -u CLAUDE_CODE_CHILD_SESSION -u CLAUDE_CODE_SESSION_ID -u CLAUDE_PID + # -u CLAUDE_JOB_DIR drops an inherited parent Claude session identity that would + # otherwise reach a firstmate-launched ordinary worker or secondmate. Claude Code + # sets those variables for its own intentional child sessions; when a primary + # (or ancestor) still carries them, an unsanitized launch shows + # "Transcript saving is off — inherited CLAUDE_CODE_CHILD_SESSION marker" and + # writes no resumable transcript. Clearing the identity pack makes an independent + # top-level Claude session. CLAUDE_CODE_FORCE_SESSION_PERSISTENCE=1 also re-enables + # saving while leaving child-session semantics in place, so it is not the chosen + # firstmate contract. Scoped to this launch line only; non-Claude harnesses and the + # captain's shell are untouched. + claude) printf '%s' 'env -u CLAUDE_CODE_CHILD_SESSION -u CLAUDE_CODE_SESSION_ID -u CLAUDE_PID -u CLAUDE_JOB_DIR CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions __MODELFLAG____EFFORTFLAG__"$(__OPINPUT__ encode launch-brief < __BRIEF__)"' ;; codex) if [ "$kind" = secondmate ]; then printf '%s' 'codex __MODELFLAG____EFFORTFLAG__--dangerously-bypass-approvals-and-sandbox "$(__OPINPUT__ encode launch-brief < __BRIEF__)"' diff --git a/docs/verification/supervision.md b/docs/verification/supervision.md index 4063f5566dc..250189a0a70 100644 --- a/docs/verification/supervision.md +++ b/docs/verification/supervision.md @@ -126,9 +126,38 @@ tests/fm-pi-watch-extension.test.sh tests/fm-watcher-lock.test.sh tests/fm-subagent-pretool-check.test.sh tests/fm-claude-stop-autoarm.test.sh +tests/fm-claude-session-env.test.sh tests/fm-turnend-guard.test.sh ``` +## Claude launch session-identity sanitize + +Verified on 2026-07-27 with Claude Code 2.1.220. +`bin/fm-spawn.sh`'s Claude launch template is the single owner: every firstmate-launched ordinary Claude worker and Claude secondmate runs under + +```sh +env -u CLAUDE_CODE_CHILD_SESSION -u CLAUDE_CODE_SESSION_ID -u CLAUDE_PID -u CLAUDE_JOB_DIR \ + CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false \ + claude --dangerously-skip-permissions ... +``` + +Comparison under a polluted parent identity (`CLAUDE_CODE_CHILD_SESSION=1`, parent `CLAUDE_CODE_SESSION_ID`, `CLAUDE_PID`, `CLAUDE_JOB_DIR`): + +| Launch environment | Transcript-saving warning | Saved independent transcript | Notes | +| --- | --- | --- | --- | +| Inherited child marker pack (pre-fix template only set prompt-suggestion) | Yes: `Transcript saving is off — inherited CLAUDE_CODE_CHILD_SESSION marker` | No | Reproduces the inherited-marker defect. | +| CLI-suggested `CLAUDE_CODE_FORCE_SESSION_PERSISTENCE=1` with child marker kept | No | Yes, new session id | Re-enables saving while leaving child-session semantics. | +| Unset `CLAUDE_CODE_CHILD_SESSION` only | No | Yes, new session id | Root cause clear. | +| Chosen contract: unset child + session id + `CLAUDE_PID` + `CLAUDE_JOB_DIR` | No | Yes, new session id | Independent top-level session; parent transcript not written. | + +Resume proof under the chosen contract (polluted outer env, sanitized launch): create turned the warning off, wrote a non-parent session transcript, and `claude --resume ` reloaded prior context with the warning still off. +No transcript contents or credentials are recorded here. + +Deterministic regression: `tests/fm-claude-session-env.test.sh` pins the launch line for ship and secondmate spawns and evaluates the prefix against a fake `claude` that asserts the markers are unset while unrelated environment is preserved. + +Runtime backends: tmux, herdr, zellij, orca, and cmux all type the shared `launch_template` command into the worker shell, so the sanitize rides every spawn-capable backend without per-backend copies. +Non-Claude harnesses are unchanged. + ## Wedge-alarm channels The two real notification channels were bounded manually on 2026-07-10 on macOS 26.5.2 with Herdr 0.7.3. diff --git a/tests/fm-backend-orca.test.sh b/tests/fm-backend-orca.test.sh index 66c3dd36535..d81e9793a8e 100755 --- a/tests/fm-backend-orca.test.sh +++ b/tests/fm-backend-orca.test.sh @@ -502,7 +502,7 @@ test_spawn_writes_orca_metadata_and_launches_harness() { "spawn should reuse the implicit terminal returned by Orca worktree creation" assert_contains "$(cat "$log")" $'orca\x1f''terminal'$'\x1f''send'$'\x1f''--terminal'$'\x1f''term-spawn'$'\x1f''--text'$'\x1f''export GOTMPDIR=/tmp/fm-orcaspawnz1/gotmp'$'\x1f''--enter'$'\x1f''--json' \ "spawn did not export GOTMPDIR through the Orca terminal" - assert_contains "$(cat "$log")" "CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions" \ + assert_contains "$(cat "$log")" "env -u CLAUDE_CODE_CHILD_SESSION -u CLAUDE_CODE_SESSION_ID -u CLAUDE_PID -u CLAUDE_JOB_DIR CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions" \ "spawn did not send the selected harness launch command through Orca" rm -rf "/tmp/fm-$id" pass "fm-spawn.sh --backend orca: reuses implicit terminal, records metadata, launches harness" diff --git a/tests/fm-claude-session-env.test.sh b/tests/fm-claude-session-env.test.sh new file mode 100755 index 00000000000..69553e6141f --- /dev/null +++ b/tests/fm-claude-session-env.test.sh @@ -0,0 +1,233 @@ +#!/usr/bin/env bash +# Regression: Claude launch template sanitizes inherited parent session identity. +# +# When a firstmate primary (or any ancestor) still carries Claude Code's +# intentional child-session environment, an unsanitized Claude crewmate or +# secondmate launch inherits CLAUDE_CODE_CHILD_SESSION and related identity +# variables. Claude then disables transcript saving with: +# "Transcript saving is off — inherited CLAUDE_CODE_CHILD_SESSION marker" +# and the worker has no independently resumable session. +# +# The fix is owned by bin/fm-spawn.sh's Claude launch_template: a per-launch +# `env -u ...` prefix clears the parent identity pack for firstmate-launched +# Claude direct reports only. These tests pin that launch line for ordinary +# workers and secondmates, prove the prefix actually drops the markers while +# preserving unrelated environment, and leave live CLI evidence to maintainer +# verification docs (no transcript contents or credentials here). +set -u + +# shellcheck source=tests/lib.sh +. "$(dirname "${BASH_SOURCE[0]}")/lib.sh" + +SPAWN="$ROOT/bin/fm-spawn.sh" +TMP_ROOT=$(fm_test_tmproot fm-claude-session-env) +PYTHON_BIN=$(command -v python3) || fail "test needs python3" +PYTHON_BIN_DIR=$(dirname "$PYTHON_BIN") +BASE_PATH=${FM_TEST_BASE_PATH:-$PYTHON_BIN_DIR:/usr/bin:/bin:/usr/sbin:/sbin} + +CLAUDE_SANITIZE_PREFIX='env -u CLAUDE_CODE_CHILD_SESSION -u CLAUDE_CODE_SESSION_ID -u CLAUDE_PID -u CLAUDE_JOB_DIR CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false' + +make_launch_capturing_tmux() { + local dir=$1 fakebin + fakebin=$(fm_fakebin "$dir") + cat > "$fakebin/tmux" <<'SH' +#!/usr/bin/env bash +set -u +case "$*" in + *"#{pane_current_path}"*) printf '%s\n' "${FM_FAKE_PANE_PATH:-}"; exit 0 ;; +esac +case "${1:-}" in + display-message) printf 'firstmate\n'; exit 0 ;; + list-windows) exit 0 ;; + has-session|new-session|new-window|kill-window) exit 0 ;; + send-keys) + if [ -n "${FM_FAKE_LAUNCH_LOG:-}" ]; then + prev= + for a in "$@"; do + if [ "$prev" = "-l" ]; then + printf '%s\n' "$a" >> "$FM_FAKE_LAUNCH_LOG" + fi + prev=$a + done + fi + exit 0 + ;; +esac +exit 0 +SH + chmod +x "$fakebin/tmux" + fm_fake_exit0 "$fakebin" treehouse + printf '%s\n' "$fakebin" +} + +make_seeded_secondmate_home() { + local home=$1 id=$2 + mkdir -p "$home/bin" "$home/data" "$home/state" "$home/config" "$home/projects" + printf '# Firstmate\n' > "$home/AGENTS.md" + printf '%s\n' "$id" > "$home/.fm-secondmate-home" + printf 'charter for %s\n' "$id" > "$home/data/charter.md" +} + +# Extract the last non-export launch command from a capturing launch log. +last_launch_command() { + local log=$1 + # Spawn sends `export GOTMPDIR=...` then the harness launch line. + grep -v '^export GOTMPDIR=' "$log" | grep -v '^$' | tail -1 +} + +test_claude_launch_template_source_contains_sanitize_prefix() { + local line + line=" claude) printf '%s' '${CLAUDE_SANITIZE_PREFIX} claude --dangerously-skip-permissions __MODELFLAG____EFFORTFLAG__\"\$(__OPINPUT__ encode launch-brief < __BRIEF__)\"' ;;" + grep -Fqx -- "$line" "$SPAWN" \ + || fail "claude launch_template lost the session-identity sanitize prefix"$'\n'"expected source line:"$'\n'"$line" + pass "claude launch_template source pins env -u parent session identity + prompt-suggestion suppress" +} + +test_ordinary_claude_worker_launch_sanitizes_parent_session_env() { + local case_dir home proj wt fakebin launchlog id out status launch + case_dir="$TMP_ROOT/ship" + home="$case_dir/home" + proj="$case_dir/project" + wt="$case_dir/wt" + launchlog="$case_dir/launch.log" + id=claude-sess-ship-z1 + fakebin=$(make_launch_capturing_tmux "$case_dir/fake") + mkdir -p "$home/data/$id" "$home/projects" "$home/state" "$home/config" + printf 'claude\n' > "$home/config/crew-harness" + fm_git_worktree "$proj" "$wt" "wt-$id" + printf 'brief for %s\n' "$id" > "$home/data/$id/brief.md" + : > "$launchlog" + + out=$( + FM_ROOT_OVERRIDE='' FM_HOME="$home" \ + FM_STATE_OVERRIDE="$home/state" FM_DATA_OVERRIDE="$home/data" \ + FM_PROJECTS_OVERRIDE="$home/projects" FM_CONFIG_OVERRIDE="$home/config" \ + FM_SPAWN_NO_GUARD=1 FM_FAKE_PANE_PATH="$wt" TMUX="fake,1,0" \ + FM_FAKE_LAUNCH_LOG="$launchlog" PATH="$fakebin:$BASE_PATH" \ + "$SPAWN" "$id" "$proj" claude 2>&1 + ) + status=$? + expect_code 0 "$status" "ordinary claude worker spawn should succeed" + assert_contains "$out" "spawned $id harness=claude" "ship spawn did not report claude" + + launch=$(last_launch_command "$launchlog") + assert_contains "$launch" "$CLAUDE_SANITIZE_PREFIX claude --dangerously-skip-permissions" \ + "ordinary claude launch missing sanitize prefix" + assert_contains "$launch" "encode launch-brief" \ + "ordinary claude launch missing brief encoding" + # Non-Claude harness markers must not appear on this launch line. + case "$launch" in + *codex*|*opencode*|*grok*|*' pi '*|*kimi*) fail "claude ship launch mixed foreign harness tokens: $launch" ;; + esac + pass "ordinary Claude worker launch includes the parent-session sanitize prefix" +} + +test_claude_secondmate_launch_sanitizes_parent_session_env() { + local case_dir primary sm fakebin launchlog out status launch + case_dir="$TMP_ROOT/secondmate" + primary="$case_dir/primary" + sm="$case_dir/sm" + launchlog="$case_dir/launch.log" + fakebin=$(make_launch_capturing_tmux "$case_dir/fake") + mkdir -p "$primary/data" "$primary/projects" "$primary/state" "$primary/config" + printf 'claude\n' > "$primary/config/crew-harness" + printf 'claude\n' > "$primary/config/secondmate-harness" + make_seeded_secondmate_home "$sm" sm-sess + : > "$launchlog" + + out=$( + FM_ROOT_OVERRIDE='' FM_HOME="$primary" \ + FM_STATE_OVERRIDE="$primary/state" FM_DATA_OVERRIDE="$primary/data" \ + FM_PROJECTS_OVERRIDE="$primary/projects" FM_CONFIG_OVERRIDE="$primary/config" \ + FM_SPAWN_NO_GUARD=1 FM_FAKE_PANE_PATH="$sm" TMUX="fake,1,0" \ + FM_FAKE_LAUNCH_LOG="$launchlog" PATH="$fakebin:$BASE_PATH" \ + "$SPAWN" sm-sess "$sm" claude --secondmate 2>&1 + ) + status=$? + expect_code 0 "$status" "claude secondmate spawn should succeed" + assert_contains "$out" "spawned sm-sess harness=claude kind=secondmate" \ + "secondmate spawn did not report claude secondmate" + + launch=$(last_launch_command "$launchlog") + assert_contains "$launch" "FM_HOME=" "secondmate launch missing FM_HOME isolation prefix" + assert_contains "$launch" "$CLAUDE_SANITIZE_PREFIX claude --dangerously-skip-permissions" \ + "claude secondmate launch missing sanitize prefix" + # Secondmate home overrides come first; sanitize prefix still reaches claude. + case "$launch" in + FM_ROOT_OVERRIDE=*\ env\ -u\ CLAUDE_CODE_CHILD_SESSION*) ;; + *) fail "secondmate launch did not keep FM_* isolation before sanitize env: $launch" ;; + esac + pass "Claude secondmate launch includes the parent-session sanitize prefix" +} + +test_sanitize_prefix_drops_parent_markers_and_preserves_unrelated_env() { + # Evaluate the same env prefix the launch template emits against a polluted + # parent environment and a fake claude that records the effective env. + local case_dir fakebin marker_file launch + case_dir="$TMP_ROOT/env-eval" + fakebin="$case_dir/fakebin" + marker_file="$case_dir/claude-env.txt" + mkdir -p "$fakebin" + cat > "$fakebin/claude" <<'SH' +#!/usr/bin/env bash +set -u +{ + printf 'CHILD=%s\n' "${CLAUDE_CODE_CHILD_SESSION-}" + printf 'SID=%s\n' "${CLAUDE_CODE_SESSION_ID-}" + printf 'PID=%s\n' "${CLAUDE_PID-}" + printf 'JOB=%s\n' "${CLAUDE_JOB_DIR-}" + printf 'PROMPT_SUGGESTION=%s\n' "${CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION-}" + printf 'UNRELATED=%s\n' "${FM_TEST_UNRELATED_ENV-}" + printf 'CLAUDECODE=%s\n' "${CLAUDECODE-}" +} > "${FM_FAKE_CLAUDE_ENV_OUT:?}" +exit 0 +SH + chmod +x "$fakebin/claude" + + launch="$CLAUDE_SANITIZE_PREFIX claude --dangerously-skip-permissions" + # shellcheck disable=SC2086 + env \ + CLAUDE_CODE_CHILD_SESSION=1 \ + CLAUDE_CODE_SESSION_ID=parent-session-id-for-test \ + CLAUDE_PID=12345 \ + CLAUDE_JOB_DIR=/tmp/parent-job-dir \ + CLAUDECODE=1 \ + FM_TEST_UNRELATED_ENV=keep-me \ + FM_FAKE_CLAUDE_ENV_OUT="$marker_file" \ + PATH="$fakebin:$BASE_PATH" \ + bash -c "$launch" + + [ -f "$marker_file" ] || fail "fake claude did not write env capture" + # assert_grep is fixed-string (grep -F); match whole lines without regex anchors. + assert_grep 'CHILD=' "$marker_file" "CHILD_SESSION was not cleared" + assert_grep 'SID=' "$marker_file" "SESSION_ID was not cleared" + assert_grep 'PID=' "$marker_file" "CLAUDE_PID was not cleared" + assert_grep 'JOB=' "$marker_file" "CLAUDE_JOB_DIR was not cleared" + assert_grep 'PROMPT_SUGGESTION=false' "$marker_file" "prompt-suggestion suppress missing" + assert_grep 'UNRELATED=keep-me' "$marker_file" "unrelated environment was not preserved" + assert_grep 'CLAUDECODE=1' "$marker_file" "unrelated CLAUDECODE should still pass through" + pass "sanitize prefix unsets parent Claude identity and preserves unrelated environment" +} + +test_non_claude_launch_templates_untouched() { + # Guardrail: the identity sanitize is Claude-only and must not leak into + # other harness launch templates. + if grep -n "CLAUDE_CODE_CHILD_SESSION" "$SPAWN" | grep -v "claude)" | grep -v '#' >/dev/null 2>&1; then + # Allow only the claude) template line (and comments) to mention the marker. + local hits + hits=$(grep -n "CLAUDE_CODE_CHILD_SESSION" "$SPAWN" | grep -v '^[[:space:]]*#' || true) + case "$hits" in + *'claude) printf'*) ;; + *) fail "CLAUDE_CODE_CHILD_SESSION sanitize leaked outside the claude launch template:"$'\n'"$hits" ;; + esac + fi + ! grep -E "env -u CLAUDE_CODE_CHILD_SESSION" "$SPAWN" | grep -E 'codex|opencode|pi|grok|kimi' >/dev/null \ + || fail "non-claude launch template incorrectly carries Claude session sanitize" + pass "non-Claude launch templates remain free of Claude session-identity sanitize" +} + +test_claude_launch_template_source_contains_sanitize_prefix +test_ordinary_claude_worker_launch_sanitizes_parent_session_env +test_claude_secondmate_launch_sanitizes_parent_session_env +test_sanitize_prefix_drops_parent_markers_and_preserves_unrelated_env +test_non_claude_launch_templates_untouched diff --git a/tests/fm-kimi-harness.test.sh b/tests/fm-kimi-harness.test.sh index 9e0f450439b..b16a5d1358a 100755 --- a/tests/fm-kimi-harness.test.sh +++ b/tests/fm-kimi-harness.test.sh @@ -20,7 +20,7 @@ assert_source_line() { } test_existing_launch_templates_are_byte_pinned() { - assert_source_line " claude) printf '%s' 'CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions __MODELFLAG____EFFORTFLAG__\"\$(__OPINPUT__ encode launch-brief < __BRIEF__)\"' ;;" + assert_source_line " claude) printf '%s' 'env -u CLAUDE_CODE_CHILD_SESSION -u CLAUDE_CODE_SESSION_ID -u CLAUDE_PID -u CLAUDE_JOB_DIR CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions __MODELFLAG____EFFORTFLAG__\"\$(__OPINPUT__ encode launch-brief < __BRIEF__)\"' ;;" assert_source_line " printf '%s' 'codex __MODELFLAG____EFFORTFLAG__--dangerously-bypass-approvals-and-sandbox \"\$(__OPINPUT__ encode launch-brief < __BRIEF__)\"'" assert_source_line " printf '%s' 'codex __MODELFLAG____EFFORTFLAG__--dangerously-bypass-approvals-and-sandbox -c \"notify=[\\\"bash\\\",\\\"-c\\\",\\\"touch __TURNEND__\\\"]\" \"\$(__OPINPUT__ encode launch-brief < __BRIEF__)\"'" assert_source_line " opencode) printf '%s' 'OPENCODE_CONFIG_CONTENT='\\''{\"permission\":{\"*\":\"allow\"}}'\\'' opencode __MODELFLAG__--prompt \"\$(__OPINPUT__ encode launch-brief < __BRIEF__)\"' ;;" diff --git a/tests/fm-spawn-dispatch-profile.test.sh b/tests/fm-spawn-dispatch-profile.test.sh index 4f587a9a398..d893dc10f34 100755 --- a/tests/fm-spawn-dispatch-profile.test.sh +++ b/tests/fm-spawn-dispatch-profile.test.sh @@ -118,7 +118,7 @@ test_no_profile_keeps_claude_profile_defaults() { assert_meta_profile "$HOME_DIR/state/$id.meta" claude default default launch=$(cat "$LAUNCH_LOG") - expected="CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions \"\$('${ROOT}/bin/fm-operational-input.sh' encode launch-brief < '$HOME_DIR/data/$id/brief.md')\"" + expected="env -u CLAUDE_CODE_CHILD_SESSION -u CLAUDE_CODE_SESSION_ID -u CLAUDE_PID -u CLAUDE_JOB_DIR CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions \"\$('${ROOT}/bin/fm-operational-input.sh' encode launch-brief < '$HOME_DIR/data/$id/brief.md')\"" [ "$launch" = "$expected" ] || fail "no-profile claude launch did not use the canonical launch kind"$'\n'"expected: $expected"$'\n'"actual: $launch" pass "no --model/--effort records defaults and types the claude launch instructions" } @@ -219,8 +219,8 @@ test_claude_threads_model_and_effort() { expect_code 0 "$status" "claude spawn with profile flags should succeed" assert_meta_profile "$HOME_DIR/state/$id.meta" claude sonnet high launch=$(cat "$LAUNCH_LOG") - assert_contains "$launch" "claude --dangerously-skip-permissions --model 'sonnet' --effort 'high'" \ - "claude launch did not thread model and effort flags" + assert_contains "$launch" "env -u CLAUDE_CODE_CHILD_SESSION -u CLAUDE_CODE_SESSION_ID -u CLAUDE_PID -u CLAUDE_JOB_DIR CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude --dangerously-skip-permissions --model 'sonnet' --effort 'high'" \ + "claude launch did not thread model and effort flags under the session-identity sanitize prefix" pass "claude receives --model and --effort profile flags" }