Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion bin/fm-spawn.sh
Original file line number Diff line number Diff line change
Expand Up @@ -450,7 +450,15 @@ 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 ANTHROPIC_API_KEY scopes out any ambient key (e.g. one set on the
# herdr server process this pane inherits from) so claude's own auth-source
# check always prefers the claude.ai OAuth login over API-key billing.
# Verified empirically (claude doctor) that an assignment to empty
# (ANTHROPIC_API_KEY=) is also treated as absent by claude's own check, but
# env -u is used here as the unambiguous true-unset idiom. Scoped to this
# one launch command; never touches the captain's own environment or
# dotfiles.
claude) printf '%s' 'env -u ANTHROPIC_API_KEY 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__)"'
Expand Down
36 changes: 34 additions & 2 deletions tests/fm-spawn-dispatch-profile.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -123,7 +123,7 @@ test_no_profile_keeps_claude_profile_defaults() {
assert_meta_profile "$HOME_DIR/state/$id.meta" claude default default

launch=$(cat "$LAUNCH_LOG")
expected="FM_HOME_BINDING= 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="FM_HOME_BINDING= env -u ANTHROPIC_API_KEY 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"
}
Expand Down Expand Up @@ -599,7 +599,7 @@ test_claude_forwards_firstmate_config_dir_when_set() {
status=$?
expect_code 0 "$status" "claude spawn with CLAUDE_CONFIG_DIR set should succeed"
launch=$(cat "$LAUNCH_LOG")
assert_contains "$launch" "CLAUDE_CONFIG_DIR='/opt/test/claude-work' CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude" \
assert_contains "$launch" "CLAUDE_CONFIG_DIR='/opt/test/claude-work' env -u ANTHROPIC_API_KEY CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude" \
"claude launch did not forward firstmate's CLAUDE_CONFIG_DIR to the crewmate pane"
pass "claude forwards firstmate's CLAUDE_CONFIG_DIR so the crewmate uses the same credential store"
}
Expand Down Expand Up @@ -637,6 +637,36 @@ test_non_claude_harness_ignores_config_dir() {
pass "non-claude harnesses do not receive the claude CLAUDE_CONFIG_DIR prefix"
}

test_claude_scopes_out_ambient_anthropic_api_key() {
local rec id out status launch
id=profile-claude-authkey-z20
rec=$(make_spawn_case profile-claude-authkey claude "$id")
read_case_record "$rec"

out=$(run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" "$id" "$PROJ_DIR")
status=$?
expect_code 0 "$status" "claude spawn should succeed"
launch=$(cat "$LAUNCH_LOG")
assert_contains "$launch" "env -u ANTHROPIC_API_KEY CLAUDE_CODE_ENABLE_PROMPT_SUGGESTION=false claude" \
"claude launch must scope out any ambient ANTHROPIC_API_KEY so OAuth login wins over API-key billing"
pass "claude launch unsets ANTHROPIC_API_KEY ahead of the claude binary"
}

test_non_claude_harness_does_not_scope_anthropic_api_key() {
local rec id out status launch
id=profile-codex-authkey-z21
rec=$(make_spawn_case profile-codex-authkey codex "$id")
read_case_record "$rec"

out=$(run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" "$id" "$PROJ_DIR")
status=$?
expect_code 0 "$status" "codex spawn should succeed"
launch=$(cat "$LAUNCH_LOG")
assert_not_contains "$launch" "ANTHROPIC_API_KEY" \
"non-claude harness launch must not be touched by the claude-specific ANTHROPIC_API_KEY scoping"
pass "non-claude harnesses are unaffected by the claude ANTHROPIC_API_KEY fix"
}

test_active_dispatch_profile_does_not_block_secondmate_launch() {
local rec id sm out status
id=profile-secondmate-z16
Expand Down Expand Up @@ -680,6 +710,8 @@ test_batch_forwards_shared_profile_flags
test_claude_forwards_firstmate_config_dir_when_set
test_claude_omits_config_dir_prefix_when_unset
test_non_claude_harness_ignores_config_dir
test_claude_scopes_out_ambient_anthropic_api_key
test_non_claude_harness_does_not_scope_anthropic_api_key
test_active_dispatch_profile_does_not_block_secondmate_launch

echo "# all fm-spawn-dispatch-profile tests passed"
Loading