Skip to content

fix: harden Claude credential guards for launches and relaunches - #7

Merged
MrGTV-love merged 11 commits into
mainfrom
fm/fm-api-key-guard-fork-pr6
Oct 6, 2026
Merged

MrGTV-love merged 11 commits into
mainfrom
fm/fm-api-key-guard-fork-pr6

Conversation

@MrGTV-love

@MrGTV-love MrGTV-love commented Sep 30, 2026 •

Copy link
Copy Markdown
Owner

Intent

ship

(Captain's board-88 answer, 2026-09-28, on the card for the two API-key guard commits that never landed.) Later, 2026-09-29: "I don't like dangling work. Why was it not pushed, PR'd and merged?"

Context: fork PR 2 (#2, "refuse Claude worker launch when an Anthropic API key would reach it") landed the first two commits of branch fm/fm-claude-worker-api-key-guard. Two later fix commits never shipped: 958c95ed "Fix round 2: extract guard to shared lib, add fm-control pre-check, ..." and 341b4d0c "Fix R3-1: apply env -u ANTHROPIC_API_KEY -u ANTHROPIC_AUTH_TOKEN to ...". Source copy: ~/.treehouse/firstmate-7bab20/10/firstmate (task fm-claude-worker-api-key-guard; keep it until this PR merges).

What Changed

  • Share the Claude API-key guard and launch-allowlist parsing between spawn and control; check replacement credentials, account pins, allowlists, and tmux environments before stopping a worker for relaunch.
  • Strip both Anthropic credential variables from non-opt-in Claude launches, including credentials captured by existing panes, and apply credential shedding and account pins across entire compound launch expressions.
  • Document the guard and POSIX shell behavior, and add regression coverage for pre-stop refusals, replacement profiles, captured credentials, compound launches, billing opt-ins, and non-tmux guards.

Risk Assessment

🚨 High: The claimed durable credential containment remains bypassable through the supported compound raw-command interface, allowing unintended API billing without explicit opt-in.

Testing

Reproduced the baseline credential escape and verified the target through real Claude/Codex CLI launches, isolated tmux environment checks, persisted metadata, and unchanged foreground-Claude state after refused relaunches. Focused guard/control regressions passed after resolving the initial driver timeout; successful authenticated account-pin launches could not be exercised live. Product transcripts and observations were saved, and disposable validation state was removed.

  • Live validation: ✅ go - 8 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Launching a Claude worker with either ambient Anthropic credential refuses before provisioning ✅ pass live Live spawn and control CLI transcript; fresh spawn created neither worker window nor task metadata. Managed and raw relaunches also refused.
Credentials retained by tmux session or global state are refused without revealing their values ✅ pass live Live spawn and control CLI transcript records refusals naming the actual tmux scope, without printing the credential sentinel.
A session removal marker or empty session value suppresses a global credential ✅ pass live Live CLI transcript records successful real compound Claude launches for both session override cases.
An allowlist retaining a caller credential refuses launch, while a filtering allowlist permits a credential-free launch ✅ pass live Live CLI refusals name config/launch-env-allowlist; compound-launch observations show both credentials unset with the filtering allowlist.
A stale pane cannot leak either credential through a compound non-opt-in Claude launch ✅ pass live Baseline-versus-target log reproduced leakage before the fix. Target observations showed both credentials unset around both real Claude version invocations, including when the allowlist retained both…
Explicit API billing opt-in preserves pane credentials and survives control relaunch ✅ pass live Compound observations retain both pane sentinels with opt-in, with and without a retaining allowlist. Actual control relaunch succeeded and persisted api_key=allow; direct spawn relaunch without the f…
A refused control relaunch leaves the existing real Claude process untouched ✅ pass live Running-Claude pre-stop artifact shows identical foreground process identity and unchanged terminal contents after both ambient credential refusals. Idle-endpoint checks also covered tmux-session/glob…
A non-Claude replacement remains launchable with an Anthropic credential present ✅ pass live Real codex --version replacement succeeded and emitted codex-cli 0.160.0 in the terminal transcript.
Authenticated ordinary and named account pins shed credentials across the entire compound launch, including explicit opt-in ⏸️ untested no Tried both supported pin forms against isolated accounts; the real Claude authentication check reported them not signed in and refused launch. The machine's normal login was not declared unavailable,…
Evidence: Live spawn and control CLI transcript

Source: Live spawn and control CLI transcript

## Refuse ANTHROPIC_API_KEY managed
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness claude
exit=1
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
error: ANTHROPIC_API_KEY is set and would reach the claude worker through ambient environment inheritance; unset it or pass --allow-api-key to deliberately bill the API


## Refuse ANTHROPIC_API_KEY raw
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness 'claude --version && claude --version'
exit=1
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
error: ANTHROPIC_API_KEY is set and would reach the claude worker through ambient environment inheritance; unset it or pass --allow-api-key to deliberately bill the API


## Refuse ANTHROPIC_AUTH_TOKEN managed
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness claude
exit=1
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
error: ANTHROPIC_AUTH_TOKEN is set and would reach the claude worker through ambient environment inheritance; unset it or pass --allow-api-key to deliberately bill the API


## Refuse ANTHROPIC_AUTH_TOKEN raw
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness 'claude --version && claude --version'
exit=1
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
error: ANTHROPIC_AUTH_TOKEN is set and would reach the claude worker through ambient environment inheritance; unset it or pass --allow-api-key to deliberately bill the API


## Refuse tmux session credential
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness claude
exit=1
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
error: ANTHROPIC_API_KEY is set in the tmux session environment and would reach the claude worker; unset it (tmux set-environment -t primary -u ANTHROPIC_API_KEY) or pass --allow-api-key to deliberately bill the API


## Refuse tmux global credential
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness claude
exit=1
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
error: ANTHROPIC_AUTH_TOKEN is set in the tmux global environment and would reach the claude worker; unset it (tmux set-environment -g -u ANTHROPIC_AUTH_TOKEN) or pass --allow-api-key to deliberately bill the API


## Refuse allowlisted ANTHROPIC_API_KEY
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness claude
exit=1
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
error: ANTHROPIC_API_KEY is set and would reach the claude worker through config/launch-env-allowlist; unset it or pass --allow-api-key to deliberately bill the API


## Refuse allowlisted ANTHROPIC_AUTH_TOKEN
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness claude
exit=1
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
error: ANTHROPIC_AUTH_TOKEN is set and would reach the claude worker through config/launch-env-allowlist; unset it or pass --allow-api-key to deliberately bill the API


## Compound raw Claude launch in stale-credential pane
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness 'claude --version && printf '"'"'first: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" > ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-ambient.txt && claude --version && printf '"'"'second: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" >> ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-ambient.txt'
exit=0
spawned guard-live-01 harness=claude kind=scout window=primary:fm-guard-live-01 worktree=~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l


## allowlist-retains compound raw Claude
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness 'claude --version && printf '"'"'first: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" > ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-allowlist-retains.txt && claude --version && printf '"'"'second: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" >> ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-allowlist-retains.txt'
exit=0
spawned guard-live-01 harness=claude kind=scout window=primary:fm-guard-live-01 worktree=~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l


## allowlist-filters compound raw Claude
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness 'claude --version && printf '"'"'first: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" > ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-allowlist-filters.txt && claude --version && printf '"'"'second: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" >> ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-allowlist-filters.txt'
exit=0
spawned guard-live-01 harness=claude kind=scout window=primary:fm-guard-live-01 worktree=~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l


## opt-in compound raw Claude
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness 'claude --version && printf '"'"'first: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" > ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-opt-in.txt && claude --version && printf '"'"'second: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" >> ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-opt-in.txt' --allow-api-key
exit=0
spawned guard-live-01 harness=claude kind=scout window=primary:fm-guard-live-01 worktree=~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l


## opt-in-allowlist compound raw Claude
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness 'claude --version && printf '"'"'first: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" > ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-opt-in-allowlist.txt && claude --version && printf '"'"'second: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" >> ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-opt-in-allowlist.txt' --allow-api-key
exit=0
spawned guard-live-01 harness=claude kind=scout window=primary:fm-guard-live-01 worktree=~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HB

... [5587 bytes truncated] ...

rst with bin/fm-control.sh guard-live-01 exit)


## Non-Claude actual Codex version launch
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness 'codex --version'
exit=0
spawned guard-live-01 harness=codex kind=scout window=primary:fm-guard-live-01 worktree=~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l


## Control carries recorded explicit opt-in
$ bin/fm-control.sh guard-live-01 relaunch --harness claude --note 'Keep deliberately opted-in API billing'
exit=1
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
error: task guard-live-01's composer state is 'unknown', not proven empty; refusing to type the /quit exit command because it could concatenate onto existing text. Clear the composer, then retry 'relaunch'
error: relaunch of guard-live-01 failed while stopping the old agent, which is still running; its original instructions were restored


## Control recorded opt-in after version command completes
$ bin/fm-control.sh guard-live-01 relaunch --harness claude --note 'Keep deliberately opted-in API billing'
exit=0
relaunched guard-live-01 harness=claude from=codex model=default effort=default backend=tmux endpoint=primary:fm-guard-live-01 worktree=~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l


## Attempt real account pin ordinary
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness 'claude --version && claude --version' --allow-api-key
exit=1
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
error: task guard-live-01's endpoint reads 'alive'; a relaunch requires a positively agent-free endpoint (stop the agent first with bin/fm-control.sh guard-live-01 exit)


## Attempt real account pin ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/account
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness 'claude --version && claude --version' --allow-api-key
exit=1
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
error: task guard-live-01's endpoint reads 'alive'; a relaunch requires a positively agent-free endpoint (stop the agent first with bin/fm-control.sh guard-live-01 exit)


## Real isolated account-pin authentication ordinary
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness 'claude --version && claude --version' --allow-api-key
exit=1
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
error: config/claude-account pins Claude workers to the ordinary account, which is not signed in (claude auth status); sign in with env -u CLAUDE_CONFIG_DIR claude, then /login, or change the pin


## Real isolated account-pin authentication ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/account
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness 'claude --version && claude --version' --allow-api-key
exit=1
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
error: config/claude-account pins Claude workers to ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/account, which is not signed in (claude auth status); sign in with CLAUDE_CONFIG_DIR=~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/account claude, then /login, or change the pin


## Session removed override suppresses global credential
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness 'claude --version && printf '"'"'first: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" > ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-ambient.txt && claude --version && printf '"'"'second: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" >> ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-ambient.txt'
exit=0
spawned guard-live-01 harness=claude kind=scout window=primary:fm-guard-live-01 worktree=~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l


## Session empty override suppresses global credential
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness 'claude --version && printf '"'"'first: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" > ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-ambient.txt && claude --version && printf '"'"'second: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" >> ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-ambient.txt'
exit=0
spawned guard-live-01 harness=claude kind=scout window=primary:fm-guard-live-01 worktree=~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l


## Target after baseline compound escape reproduction
$ bin/fm-spawn.sh guard-live-01 --relaunch --harness 'claude --version && printf '"'"'first: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" > ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-corrected.txt && claude --version && printf '"'"'second: API_KEY=%s AUTH_TOKEN=%s\n'"'"' "${ANTHROPIC_API_KEY-unset}" "${ANTHROPIC_AUTH_TOKEN-unset}" >> ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/observed-corrected.txt'
exit=0
spawned guard-live-01 harness=claude kind=scout window=primary:fm-guard-live-01 worktree=~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l


## Fresh spawn refusal before provisioning ANTHROPIC_API_KEY
$ bin/fm-spawn.sh guard-fresh-02 ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/p --scout --harness claude --backend tmux
exit=1
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
error: ANTHROPIC_API_KEY is set and would reach the claude worker through ambient environment inheritance; unset it or pass --allow-api-key to deliberately bill the API


## Fresh spawn refusal before provisioning ANTHROPIC_AUTH_TOKEN
$ bin/fm-spawn.sh guard-fresh-02 ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/p --scout --harness claude --backend tmux
exit=1
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l
error: ANTHROPIC_AUTH_TOKEN is set and would reach the claude worker through ambient environment inheritance; unset it or pass --allow-api-key to deliberately bill the API
Evidence: Baseline leakage versus target credential containment

Source: Baseline leakage versus target credential containment

Base commit: 13f00a597029189f5f6594c3952a47b41fb9a21b
Base fm-spawn.sh executed via bash -c with SCRIPT_DIR pointing at this worktree bin; all live pane/product inputs identical.
spawned guard-live-01 harness=claude kind=scout window=primary:fm-guard-live-01 worktree=~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l

first: API_KEY=stale-pane-sentinel AUTH_TOKEN=stale-token-sentinel
second: API_KEY=stale-pane-sentinel AUTH_TOKEN=stale-token-sentinel

Target commit: 47206a405e2780e91940b322d8caf60f9c65019b
spawned guard-live-01 harness=claude kind=scout window=primary:fm-guard-live-01 worktree=~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l

first: API_KEY=unset AUTH_TOKEN=unset
second: API_KEY=unset AUTH_TOKEN=unset
Evidence: Compound-launch credential observations

Source: Compound-launch credential observations

ambient
first: API_KEY=unset AUTH_TOKEN=unset
second: API_KEY=unset AUTH_TOKEN=unset

allowlist-retains
first: API_KEY=unset AUTH_TOKEN=unset
second: API_KEY=unset AUTH_TOKEN=unset

allowlist-filters
first: API_KEY=unset AUTH_TOKEN=unset
second: API_KEY=unset AUTH_TOKEN=unset

opt-in
first: API_KEY=stale-pane-sentinel AUTH_TOKEN=stale-token-sentinel
second: API_KEY=stale-pane-sentinel AUTH_TOKEN=stale-token-sentinel

opt-in-allowlist
first: API_KEY=stale-pane-sentinel AUTH_TOKEN=stale-token-sentinel
second: API_KEY=stale-pane-sentinel AUTH_TOKEN=stale-token-sentinel
Evidence: Real Claude foreground process preserved by refused relaunch

Source: Real Claude foreground process preserved by refused relaunch

Before and after guard refusals: identical foreground processes and unchanged pane.
/dev/ttys109|2.1.291|120x40
45836 45836 45836 claude
66330 66330 45836 /bin/bash

2.1.291 (Claude Code)
bash-3.2$ unset ANTHROPIC_API_KEY ANTHROPIC_AUTH_TOKEN; claude
Welcome to Claude Code v2.1.291
..........................................................

     *                                       █████▓▓░
                                 *         ███▓░     ░░
            ░░░░░░                        ███▓░
    ░░░   ░░░░░░░░░░                      ███▓░
   ░░░░░░░░░░░░░░░░░░░    *                ██▓░░      ▓
                                             ░▓▓███▓▓░
 *                                 ░░░░
                                 ░░░░░░░░
                               ░░░░░░░░░░░░░░░░
       █████████                                        *
      ██▄█████▄██                        *
       █████████      *
.......█ █   █ █..........................................

 Let's get started.

 Choose the text style that looks best with your terminal
 To change this later, run /theme

     Auto (match terminal)
 ❯ ✔ Dark mode
     Light mode
     Dark mode (colorblind-friendly)
     Light mode (colorblind-friendly)
     Dark mode (ANSI colors only)
     Light mode (ANSI colors only)

 ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
  1  function greet() {
  2 -  console.log("Hello, World!");
  2 +  console.log("Hello, Claude!");
  3  }
 ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
  Syntax theme: Monokai Extended (ctrl+t to disable)
Evidence: Replacement metadata retaining explicit API billing opt-in

Source: Replacement metadata retaining explicit API billing opt-in

window=primary:fm-guard-live-01
endpoint_task_id=guard-live-01
worktree=~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w
project=~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/p
harness=claude
kind=scout
tasktmp=/tmp/fm-guard-live-01
model=default
effort=default
api_key=allow
busy_gen=g1791307127.28701.8351
spawn_gen=s1791307128.22985.17946
control_relaunch_tx=21218.20261006T171846Z.32124
Evidence: Actual terminal transcript with Claude and Codex version output

Source: Actual terminal transcript with Claude and Codex version output


The default interactive shell is now zsh.
To update your account to use zsh, please run `chsh -s /bin/zsh`.
For more details, please visit https://support.apple.com/kb/HT208050.
bash-3.2$ export ANTHROPIC_API_KEY=stale-pane-sentinel ANTHROPIC_AUTH_TOKEN=stale-token-sentinel
bash-3.2$ cd -- '~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w'
bash-3.2$ export GOTMPDIR=/tmp/fm-guard-live-01/gotmp
bash-3.2$ export COMPACT_ADVISER_DISABLE=1
bash-3.2$ export FM_TASK_ID=guard-live-01
bash-3.2$ . '/tmp/fm-guard-live-01+c5ca15a875bf6afb57b5c37bc0ef23b8a0e93884a61f39851c2602a8e774f89a/launch.s1791306909.9
2596.4537.sh'
2.1.291 (Claude Code)
2.1.291 (Claude Code)
bash-3.2$ cd -- '~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w'
bash-3.2$ export GOTMPDIR=/tmp/fm-guard-live-01/gotmp
bash-3.2$ export COMPACT_ADVISER_DISABLE=1
bash-3.2$ export FM_TASK_ID=guard-live-01
bash-3.2$ . '/tmp/fm-guard-live-01+c5ca15a875bf6afb57b5c37bc0ef23b8a0e93884a61f39851c2602a8e774f89a/launch.s1791306935.7
8905.27335.sh'
2.1.291 (Claude Code)
2.1.291 (Claude Code)
bash-3.2$ cd -- '~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w'
bash-3.2$ export GOTMPDIR=/tmp/fm-guard-live-01/gotmp
bash-3.2$ export COMPACT_ADVISER_DISABLE=1
bash-3.2$ export FM_TASK_ID=guard-live-01
bash-3.2$ . '/tmp/fm-guard-live-01+c5ca15a875bf6afb57b5c37bc0ef23b8a0e93884a61f39851c2602a8e774f89a/launch.s1791306942.9
0809.32350.sh'
2.1.291 (Claude Code)
2.1.291 (Claude Code)
bash-3.2$ cd -- '~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w'
bash-3.2$ export GOTMPDIR=/tmp/fm-guard-live-01/gotmp
bash-3.2$ export COMPACT_ADVISER_DISABLE=1
bash-3.2$ export FM_TASK_ID=guard-live-01
bash-3.2$ . '/tmp/fm-guard-live-01+c5ca15a875bf6afb57b5c37bc0ef23b8a0e93884a61f39851c2602a8e774f89a/launch.s1791306948.1
4529.23428.sh'
2.1.291 (Claude Code)
2.1.291 (Claude Code)
bash-3.2$ cd -- '~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w'
bash-3.2$ export GOTMPDIR=/tmp/fm-guard-live-01/gotmp
bash-3.2$ export COMPACT_ADVISER_DISABLE=1
bash-3.2$ export FM_TASK_ID=guard-live-01
bash-3.2$ . '/tmp/fm-guard-live-01+c5ca15a875bf6afb57b5c37bc0ef23b8a0e93884a61f39851c2602a8e774f89a/launch.s1791306951.3
2425.8782.sh'
2.1.291 (Claude Code)
2.1.291 (Claude Code)
bash-3.2$ cd -- '~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w'
bash-3.2$ export GOTMPDIR=/tmp/fm-guard-live-01/gotmp
bash-3.2$ export COMPACT_ADVISER_DISABLE=1
bash-3.2$ export FM_TASK_ID=guard-live-01
bash-3.2$ . '/tmp/fm-guard-live-01+c5ca15a875bf6afb57b5c37bc0ef23b8a0e93884a61f39851c2602a8e774f89a/launch.s1791306956.4
3773.31933.sh'
2.1.291 (Claude Code)
2.1.291 (Claude Code)
bash-3.2$ unset ANTHROPIC_API_KEY ANTHROPIC_AUTH_TOKEN; claude
Welcome to Claude Code v2.1.291
Welcome to Claude Code v2.1.291
..........................................................

     *                                       █████▓▓░
                                 *         ███▓░     ░░
            ░░░░░░                        ███▓░
    ░░░   ░░░░░░░░░░                      ███▓░
   ░░░░░░░░░░░░░░░░░░░    *                ██▓░░      ▓
                                             ░▓▓███▓▓░
 *                                 ░░░░
                                 ░░░░░░░░
                               ░░░░░░░░░░░░░░░░
       █████████                                        *
      ██▄█████▄██                        *
       █████████      *
.......█ █   █ █..........................................

 Let's get started.

 Choose the text style that looks best with your terminal
 To change this later, run /theme

     Auto (match terminal)
 ❯ ✔ Dark mode
     Light mode
     Dark mode (colorblind-friendly)
     Light mode (colorblind-friendly)
     Dark mode (ANSI colors only)
     Light mode (ANSI colors only)

 ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
  1  function greet() {
  2 -  console.log("Hello, World!");
  2 +  console.log("Hello, Claude!");
  3  }
 ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
  Syntax theme: Monokai Extended (ctrl+t to disable)
bash-3.2$ cd -- '~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w'
bash-3.2$ export GOTMPDIR=/tmp/fm-guard-live-01/gotmp
bash-3.2$ export COMPACT_ADVISER_DISABLE=1
bash-3.2$ export FM_TASK_ID=guard-live-01
bash-3.2$ . '/tmp/fm-guard-live-01+c5ca15a875bf6afb57b5c37bc0ef23b8a0e93884a61f39851c2602a8e774f89a/launch.s1791307092.7
2603.17606.sh'
codex-cli 0.160.0
bash-3.2$ cd -- '~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w'
bash-3.2$ export GOTMPDIR=/tmp/fm-guard-live-01/gotmp
bash-3.2$ export COMPACT_ADVISER_DISABLE=1
bash-3.2$ export FM_TASK_ID=guard-live-01
bash-3.2$ . '/tmp/fm-guard-live-01+c5ca15a875bf6afb57b5c37bc0ef23b8a0e93884a61f39851c2602a8e774f89a/launch.s1791307128.2
2985.17946.sh'
Welcome to Claude Code v2.1.291
..........................................................

     *                                       █████▓▓░
                                 *         ███▓░     ░░
            ░░░░░░                        ███▓░
    ░░░   ░░░░░░░░░░                      ███▓░
   ░░░░░░░░░░░░░░░░░░░    *                ██▓░░      ▓
                                             ░▓▓███▓▓░
 *                                 ░░░░
                                 ░░░░░░░░
                               ░░░░░░░░░░░░░░░░
       █████████                                        *
      ██▄█████▄██                        *
       █████████      *
.......█ █   █ █..........................................

 Let's get started.

 Choose the text style that looks best with your terminal
 To change this later, run /theme

     Auto (match terminal)
 ❯ ✔ Dark mode
     Light mode
     Dark mode (colorblind-friendly)
     Light mode (colorblind-friendly)
     Dark mode (ANSI colors only)
     Light mode (ANSI colors only)

 ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
  1  function greet() {
  2 -  console.log("Hello, World!");
  2 +  console.log("Hello, Claude!");
  3  }
 ╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌╌
  Syntax theme: Monokai Extended (ctrl+t to disable)
bash-3.2$ cd -- '~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w'
bash-3.2$ export GOTMPDIR=/tmp/fm-guard-live-01/gotmp
bash-3.2$ export COMPACT_ADVISER_DISABLE=1
bash-3.2$ export FM_TASK_ID=guard-live-01
bash-3.2$ . '/tmp/fm-guard-live-01+c5ca15a875bf6afb57b5c37bc0ef23b8a0e93884a61f39851c2602a8e774f89a/launch.s1791307207.8
7341.19249.sh'
2.1.291 (Claude Code)
2.1.291 (Claude Code)
bash-3.2$ cd -- '~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w'
bash-3.2$ export GOTMPDIR=/tmp/fm-guard-live-01/gotmp
bash-3.2$ export COMPACT_ADVISER_DISABLE=1
bash-3.2$ export FM_TASK_ID=guard-live-01
bash-3.2$ . '/tmp/fm-guard-live-01+c5ca15a875bf6afb57b5c37bc0ef23b8a0e93884a61f39851c2602a8e774f89a/launch.s1791307211.9
7389.17384.sh'
2.1.291 (Claude Code)
2.1.291 (Claude Code)
bash-3.2$ export ANTHROPIC_API_KEY=stale-pane-sentinel ANTHROPIC_AUTH_TOKEN=stale-token-sentinel
bash-3.2$ cd -- '~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w'
bash-3.2$ export GOTMPDIR=/tmp/fm-guard-live-01/gotmp
bash-3.2$ export COMPACT_ADVISER_DISABLE=1
bash-3.2$ export FM_TASK_ID=guard-live-01
bash-3.2$ . '/tmp/fm-guard-live-01+c5ca15a875bf6afb57b5c37bc0ef23b8a0e93884a61f39851c2602a8e774f89a/launch.s1791307261.6
0812.1306.sh'
2.1.291 (Claude Code)
2.1.291 (Claude Code)
bash-3.2$ cd -- '~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/w'
bash-3.2$ export GOTMPDIR=/tmp/fm-guard-live-01/gotmp
bash-3.2$ export COMPACT_ADVISER_DISABLE=1
bash-3.2$ export FM_TASK_ID=guard-live-01
bash-3.2$ . '/tmp/fm-guard-live-01+c5ca15a875bf6afb57b5c37bc0ef23b8a0e93884a61f39851c2602a8e774f89a/launch.s1791307266.7
7673.8667.sh'
2.1.291 (Claude Code)
2.1.291 (Claude Code)
bash-3.2$
Evidence: Disposable Claude account authentication status

Source: Disposable Claude account authentication status

{
  "loggedIn": false,
  "authMethod": "none",
  "apiProvider": "firstParty",
  "analyticsDisabled": false,
  "projectsDirectory": "~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/user/.claude/projects",
  "configDirectory": "~/.no-mistakes/worktrees/32d18ed9638d/01M4919A9AR43ZS1JWZBR2HBT7/l/user/.claude"
}
Evidence: Private lab teardown and socket-path accommodation

Source: Private lab teardown and socket-path accommodation

Private tmux fm-lab server stopped and confirmed absent. Removed disposable marked lab l/ and .validation-tmp/ after unlocking their read-only generated hook directories. No source/test files modified, no credentials changed, no system packages installed. Normal operator tmux/Herdr sessions were not used. The socket was shortened to l/tmux-501/fm-lab because l/tmux/tmux-501/fm-lab exceeded the Darwin Unix socket pathname limit.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 warning
  • 🚨 bin/fm-spawn.sh:5177 - The stale-pane fix does not cover an entire supported compound raw launch. With clean caller/tmux environments, no account pin or allowlist, and an existing pane exporting ANTHROPIC_API_KEY, a raw command such as claude --version && claude --dangerously-skip-permissions ... passes preflight. The generated env -u ... cleans only the first command; the second Claude process inherits the unchanged pane credential and can bill the API without --allow-api-key. Compound raw commands are supported by tests/fm-spawn-dispatch-profile.test.sh:474, and docs/configuration.md:1019 explicitly includes raw Claude launches. Apply credential shedding at the shared execution boundary enclosing the entire launch expression, preserving opt-in behavior. Related sites: bin/fm-spawn.sh:5161 and :5163—the pinned-account prefixes likewise cover only the first raw command and must retain their broader shedding invariant; bin/fm-spawn.sh:5373—an allowlist explicitly retaining either credential leaves the same compound-command escape reachable; docs/configuration.md:1033—the unconditional captured-pane containment claim is not currently true. Both ANTHROPIC_API_KEY and ANTHROPIC_AUTH_TOKEN are affected.
  • ⚠️ tests/fm-spawn-dispatch-profile.test.sh:1614 - The change restores an unused expected-command snapshot path: claude_worker_add_dirs at :1614 is used only by claude_expected_launch at :1622, which is used only by test_claude_permission_mode_bypass_matches_absent_launch at :1631. That test is never invoked, and its expected-command helper references ai_trailer_hooks_prefix, which has no definition in the repository. No API-key guard intent requirement needs this dormant permission-mode test or parallel launch-command reconstruction. Remove the three definitions rather than activating and repairing this unrelated path.

🔧 Fix applied.
1 warning still open:

  • ⚠️ tests/fm-spawn-dispatch-profile.test.sh:1614 - The change restores an unused expected-command snapshot path: claude_worker_add_dirs at :1614 is used only by claude_expected_launch at :1622, which is used only by test_claude_permission_mode_bypass_matches_absent_launch at :1631. That test is never invoked, and its expected-command helper references ai_trailer_hooks_prefix, which has no definition in the repository. No API-key guard intent requirement needs this dormant permission-mode test or parallel launch-command reconstruction. Remove the three definitions rather than activating and repairing this unrelated path.
✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 8 of 9 scenarios driven live against the product
Scenario Result Live Evidence
Launching a Claude worker with either ambient Anthropic credential refuses before provisioning ✅ pass live Live spawn and control CLI transcript; fresh spawn created neither worker window nor task metadata. Managed and raw relaunches also refused.
Credentials retained by tmux session or global state are refused without revealing their values ✅ pass live Live spawn and control CLI transcript records refusals naming the actual tmux scope, without printing the credential sentinel.
A session removal marker or empty session value suppresses a global credential ✅ pass live Live CLI transcript records successful real compound Claude launches for both session override cases.
An allowlist retaining a caller credential refuses launch, while a filtering allowlist permits a credential-free launch ✅ pass live Live CLI refusals name config/launch-env-allowlist; compound-launch observations show both credentials unset with the filtering allowlist.
A stale pane cannot leak either credential through a compound non-opt-in Claude launch ✅ pass live Baseline-versus-target log reproduced leakage before the fix. Target observations showed both credentials unset around both real Claude version invocations, including when the allowlist retained both…
Explicit API billing opt-in preserves pane credentials and survives control relaunch ✅ pass live Compound observations retain both pane sentinels with opt-in, with and without a retaining allowlist. Actual control relaunch succeeded and persisted api_key=allow; direct spawn relaunch without the f…
A refused control relaunch leaves the existing real Claude process untouched ✅ pass live Running-Claude pre-stop artifact shows identical foreground process identity and unchanged terminal contents after both ambient credential refusals. Idle-endpoint checks also covered tmux-session/glob…
A non-Claude replacement remains launchable with an Anthropic credential present ✅ pass live Real codex --version replacement succeeded and emitted codex-cli 0.160.0 in the terminal transcript.
Authenticated ordinary and named account pins shed credentials across the entire compound launch, including explicit opt-in ⏸️ untested no Tried both supported pin forms against isolated accounts; the real Claude authentication check reported them not signed in and refused launch. The machine's normal login was not declared unavailable,…
  • claude --version confirmed the installed real CLI: Claude Code 2.1.291.
  • bin/fm-lab-home.sh create <worktree>/l created a disposable marked home; real tmux ran on its private fm-lab socket with a 120×40 pane.
  • bin/fm-spawn.sh guard-fresh-02 <disposable-project> --scout --harness claude --backend tmux refused each ambient Anthropic credential before creating a task record or worker window.
  • bin/fm-spawn.sh guard-live-01 --relaunch --harness claude and its compound raw-command variant exercised ambient, tmux-session, tmux-global, and allowlist credential refusals.
  • Real tmux set-environment operations exercised session removal markers and empty session values overriding a global credential.
  • bin/fm-spawn.sh guard-live-01 --relaunch --harness 'claude --version && ... && claude --version && ...' exercised stale-pane credentials, retaining/filtering allowlists, and --allow-api-key; shell observations recorded the environment surrounding both real Claude invocations.
  • Executed the base revision's fm-spawn.sh with only its self-location bootstrap adapted for bash -c; reproduced credential leakage, then reran the same scenario against the target and observed both credentials unset.
  • bin/fm-control.sh guard-live-01 relaunch --note 'Live pre-stop guard validation' refused ambient and tmux credentials; against a foreground real Claude process, its process identity and terminal contents remained unchanged.
  • bin/fm-spawn.sh guard-live-01 --relaunch --harness 'codex --version' succeeded with an ambient Anthropic key and produced codex-cli 0.160.0.
  • bin/fm-control.sh guard-live-01 relaunch --harness claude --note 'Keep deliberately opted-in API billing' carried recorded api_key=allow into replacement metadata.
  • claude auth status --json and actual spawn attempts with ordinary and named isolated account pins confirmed those disposable accounts were not signed in.
  • bash tests/fm-spawn-claude-api-key-guard.test.sh completed successfully with a longer allowance after the initial 180-second driver timeout.
  • Executed only five guard-related functions from tests/fm-control-relaunch.test.sh: pre-stop refusal, replacement-profile selection, tmux refusal, opt-in persistence, and direct-relaunch opt-in removal. All completed successfully; these were supplemental mock-backed regressions.
  • Killed only the private fm-lab tmux server, confirmed it was absent, checked for remaining processes referencing the disposable test root, and removed both worktree-local validation directories.
✅ **Document** - passed

✅ No issues found.

🔧 **Lint** - 1 issue found → auto-fixed ✅
  • ⚠️ linter found issues (exit code 1)

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

…ocs updates

- Extract Claude API key guard into bin/fm-api-key-guard-lib.sh for reuse
- Replace inline guard in fm-spawn.sh with library call
- Add guard pre-check in fm-control before do_exit (R2-5 option a)
- Check tmux GLOBAL environment in addition to session env (R2-1)
- Update docs for pin exemption, tmux check, pane-rc/direnv note (R2-4, R2-6)
- Fix shellcheck warnings
…very non-opt-in claude launch

- Strip credential vars from worker env for all claude launches without
  --allow-api-key, closing the relaunch-into-old-pane gap
- Document known gap in docs/configuration.md
- Add tests for the env -u prefix in launch command
…e other 2 checks do not come from this PR. 1. "Behavior portable serial 8". This PR caused it. The failing test was tests/fm-spawn-dispatch-profile.test.sh. Its message was "no-profile claude launch did not use the canonical launch kind". This PR adds `env -u ANTHROPIC_API_KEY -u ANTHROPIC_AUTH_TOKEN` to every Claude launch that does not use --allow-api-key and has no worker-account pin (bin/fm-spawn.sh:5161). That is the intended behavior. But the test helper `claude_expected_launch` (tests/fm-spawn-dispatch-profile.test.sh:1679) still built the old launch line. All 4 callers of that helper are non-opt-in launches with no pin. So I added the same credential shed to the expected line in the helper. I did not change product code. - Proof: I ran `bash tests/fm-spawn-dispatch-profile.test.sh` locally. It printed "# all fm-spawn-dispatch-profile tests passed" and exited 0. shellcheck -x on the file also passed. The test took over 20 minutes here because other jobs were loading the machine. It did not hang. 2. "Behavior portable serial 2". The failure is in tests/fm-calm-pi-extension.test.sh: "rendered export DOM violated the Calm conversation boundary". This PR did not cause it. The same test fails the same way in CI run 36658046545 on an unrelated PR (fm/fm-subagent-guard-fix, PR 6). This PR does not touch any Pi or Calm code. The main branch CI passed on 2026-09-29, so the cause is probably outside the repo (for example, a newer Pi or Chrome version in CI). I made no change. 3. "Behavior portable serial 4". The failure is in tests/fm-pi-branch-extension.test.sh: "Calm-off ToolExecutionComponent rendering differs from Pi stock". This PR did not cause it either. The same failure shows up in the PR 6 run above. I made no change. What changed: 1 line in tests/fm-spawn-dispatch-profile.test.sh. What you do now: nothing for shard 8. Shards 2 and 4 will stay red until someone fixes the Pi test drift in a separate change
…ork-pr6

# Conflicts:
#	bin/fm-control.sh
#	bin/fm-spawn.sh
#	docs/configuration.md
#	tests/fm-spawn-claude-api-key-guard.test.sh
#	tests/fm-spawn-dispatch-profile.test.sh
@MrGTV-love MrGTV-love changed the title fix(bin): check the Claude API key guard before relaunch and shed credentials from every Claude launch fix: harden Claude credential guards for launches and relaunches Oct 6, 2026
… 2, 4, 7, and 8. Their assertions depended on shell-command quoting that changed when credential shedding wrapped the complete Claude launch expression. Affected tests now execute captured launches with model-free probes and assert received argv/environment. The shared TeamClaude assertion retains executable proxy/CA checks instead of inspecting launcher spelling. Removed obsolete snapshot helpers and updated the contributor helper inventory. Production credential shedding and explicit opt-in behavior are unchanged. Verification: all six failing test scripts passed locally, plus the API-key guard and worker-account regression suites. Compound-launch credential cases passed. Stock Bash syntax checks passed for all six modified shell files, documentation validation passed, and a shell smoke verified NUL-delimited arguments preserve spaces, empty values, and newlines. An initial run used an invalid in-repository temporary secondmate layout; affected scripts passed after correcting that setup. One optional Cursor case skipped because its executable was unavailable. Some existing cleanup/BSD-sed warnings were emitted, but the scripts returned 0. Full CI shards were not rerun
@MrGTV-love
MrGTV-love merged commit 12609b8 into main Oct 6, 2026
21 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant