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: 10 additions & 0 deletions bin/fm-spawn.sh
Original file line number Diff line number Diff line change
Expand Up @@ -1490,6 +1490,16 @@ LAUNCH=${LAUNCH//__PIEXT__/$sq_piext}
LAUNCH=${LAUNCH//__PITURNEND__/$sq_piturnend}
LAUNCH=${LAUNCH//__PIWATCH__/$sq_piwatch}
LAUNCH=${LAUNCH//__OPINPUT__/$sq_opinput}
# Crewmate panes are created by a long-lived tmux/herdr daemon that does not
# inherit firstmate's current environment, so a bare `claude` in the pane falls
# back to the default ~/.claude store even when firstmate itself runs under a
# different CLAUDE_CONFIG_DIR (for example a work-vs-personal subscription split).
# Forward firstmate's own resolved store onto the claude launch so the crewmate
# uses the same credential/config firstmate is authenticated with. Only when set;
# an unset value is the single-store default and needs no prefix.
if [ "$HARNESS" = claude ] && [ -n "${CLAUDE_CONFIG_DIR:-}" ]; then
LAUNCH="CLAUDE_CONFIG_DIR=$(shell_quote "$CLAUDE_CONFIG_DIR") $LAUNCH"
fi
if [ "$KIND" = secondmate ]; then
sq_home=$(shell_quote "$PROJ_ABS")
LAUNCH="FM_ROOT_OVERRIDE= FM_STATE_OVERRIDE= FM_DATA_OVERRIDE= FM_PROJECTS_OVERRIDE= FM_CONFIG_OVERRIDE= FM_HOME=$sq_home $LAUNCH"
Expand Down
57 changes: 57 additions & 0 deletions tests/fm-spawn-dispatch-profile.test.sh
Original file line number Diff line number Diff line change
Expand Up @@ -84,10 +84,15 @@ run_spawn() {
local home=$1 wt=$2 fakebin=$3 launchlog=$4
shift 4
: > "$launchlog"
# CLAUDE_CONFIG_DIR is forwarded onto claude launches by fm-spawn, so pin it
# explicitly (empty by default) instead of leaking the invoking shell's value,
# which would make launch assertions depend on the developer's environment.
# A test opts in to the set case via FM_TEST_CLAUDE_CONFIG_DIR.
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" \
CLAUDE_CONFIG_DIR="${FM_TEST_CLAUDE_CONFIG_DIR:-}" \
FM_FAKE_LAUNCH_LOG="$launchlog" GROK_HOME="$home/grok-home" PATH="$fakebin:$PATH" \
"$SPAWN" "$@" 2>&1
}
Expand Down Expand Up @@ -436,6 +441,55 @@ test_batch_forwards_shared_profile_flags() {
pass "batch dispatch forwards shared --harness, --model, and --effort to every pair"
}

test_claude_forwards_firstmate_config_dir_when_set() {
local rec id out status launch
id=profile-claude-cfgdir-z17
rec=$(make_spawn_case profile-claude-cfgdir claude "$id")
read_case_record "$rec"

out=$(FM_TEST_CLAUDE_CONFIG_DIR="/opt/test/claude-work" \
run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" "$id" "$PROJ_DIR")
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" \
"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"
}

test_claude_omits_config_dir_prefix_when_unset() {
local rec id out status launch
id=profile-claude-nocfgdir-z18
rec=$(make_spawn_case profile-claude-nocfgdir claude "$id")
read_case_record "$rec"

# run_spawn pins CLAUDE_CONFIG_DIR empty by default, exercising the single-store
# default path where fm-spawn adds no prefix.
out=$(run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" "$id" "$PROJ_DIR")
status=$?
expect_code 0 "$status" "claude spawn without CLAUDE_CONFIG_DIR should succeed"
launch=$(cat "$LAUNCH_LOG")
assert_not_contains "$launch" "CLAUDE_CONFIG_DIR=" \
"claude launch must not add a config-dir prefix when firstmate has no CLAUDE_CONFIG_DIR set"
pass "claude omits the config-dir prefix when firstmate runs with the single-store default"
}

test_non_claude_harness_ignores_config_dir() {
local rec id out status launch
id=profile-codex-nocfgdir-z19
rec=$(make_spawn_case profile-codex-nocfgdir codex "$id")
read_case_record "$rec"

out=$(FM_TEST_CLAUDE_CONFIG_DIR="/opt/test/claude-work" \
run_spawn "$HOME_DIR" "$WT_DIR" "$FAKEBIN_DIR" "$LAUNCH_LOG" "$id" "$PROJ_DIR")
status=$?
expect_code 0 "$status" "codex spawn with CLAUDE_CONFIG_DIR set should succeed"
launch=$(cat "$LAUNCH_LOG")
assert_not_contains "$launch" "CLAUDE_CONFIG_DIR=" \
"non-claude harness launch must not receive the claude-specific config-dir prefix"
pass "non-claude harnesses do not receive the claude CLAUDE_CONFIG_DIR prefix"
}

test_active_dispatch_profile_does_not_block_secondmate_launch() {
local rec id sm out status
id=profile-secondmate-z16
Expand Down Expand Up @@ -472,6 +526,9 @@ test_pi_signed_threads_shared_pi_profile_and_preserves_identity
test_pi_signed_missing_binary_refuses_before_endpoint_or_metadata
test_pi_signed_persistent_secondmate_uses_pi_extensions_and_identity
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_active_dispatch_profile_does_not_block_secondmate_launch

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