Skip to content

fix(bin): keep the firstmate home's supervisor contract out of nested ship and scout workers - #4

Merged
Oskari-Heikkinen merged 6 commits into
mainfrom
fm/fm-nested-worker-contract
Sep 24, 2026
Merged

Oskari-Heikkinen merged 6 commits into
mainfrom
fm/fm-nested-worker-contract

Conversation

@Oskari-Heikkinen

@Oskari-Heikkinen Oskari-Heikkinen commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Intent

Lets try fixing this.

"This" is the finding from a token-usage study of the fleet: project workers supervised by a second mate have their worktrees inside that second mate's firstmate home (/projects//.treehouse///), so Claude Code loads every ancestor CLAUDE.md, including /CLAUDE.md -> @AGENTS.md, the full firstmate supervisor contract (about 87K chars, about 31K tokens), on every API call of every such worker and its subagents. The firstmate internal skills under /.claude/skills also appear in their skill listing (about 1.5K tokens). Measured: nested worker first-call context median 85-94K tokens versus 37K for a main-home worker whose pool sits outside the home; about 632M tokens over one week, about 351M tokens per day at the current rate. The same nesting triggered Claude Code's "external CLAUDE.md include" consent prompt repeatedly. Nested Pi workers show the same pattern (median first call 23.3K vs 7.4K tokens). The workers are told by their launch brief to ignore that contract anyway.

What Changed

  • bin/fm-spawn.sh now checks whether a ship or scout worktree sits inside the firstmate home that launches it, such as a second mate's in-project Treehouse pool at <home>/projects/<project>/.treehouse/.... When it does, a Claude launch gets a claudeMdExcludes list in its inline --settings JSON. The list covers the home's CLAUDE.md, CLAUDE.local.md, AGENTS.md, .claude/CLAUDE.md, and .claude/rules/**, so the @AGENTS.md import and the "external CLAUDE.md imports" prompt it caused both go away. A Pi launch's per-task extension adds a before_agent_start handler that removes those home files' <project_instructions> blocks from the system prompt on every turn. The project's own instruction files still load. Secondmates, and workers whose worktree is outside the home, launch unchanged. If the home path contains glob characters, the Claude exclusion is skipped and a warning is printed.
  • docs/configuration.md and the Claude harness-adapter reference now describe this behaviour. docs/verification/runtime-backends.md gains a "Nested worktree instruction isolation" section with evidence for each harness: Claude and Pi fixed, Codex already stops at the git root, Cursor Agent CLI and Oh My Pi still leak and can't be fixed per path. It also records that the reported .claude/skills leak did not reproduce on Claude Code 2.1.280.
  • Adds tests/fm-spawn-nested-home-context.test.sh, a spawn-level test, and tests/fm-nested-home-context-live-e2e.test.sh, a live check that captures each harness's real first request. Both are registered in bin/fm-test-run.sh.

Risk Assessment

✅ Low: The change is well bounded. It only takes effect when a ship or scout worktree is strictly inside the launching FM_HOME, and every other launch gets an empty placeholder and stays byte-identical. Claude path quoting and escaping follow existing patterns in the script. The Pi filter removes only the exact rendered block of each file that sits in the home. A unit test drives the real spawn, and a live test compares captured model requests with and without the change.

Testing

Ran the change's own live guard, which points the installed claude 2.1.281 and pi 0.85.1 at a local server that records each request instead of sending it to a provider, plus the fm-spawn behavior test; both passed. Built a realistic measurement: a throwaway copy of this repo at HEAD served as the firstmate home (real 60KB AGENTS.md, CLAUDE.md, .claude/skills). Its own fm-spawn generated the launch for a nested worker, and the first request dropped the supervisor contract for both harnesses while keeping the project's rules. Firstmate skill names also no longer appear. Drove the interactive Claude screen in a private tmux server. The baseline launch shows the external-import consent dialog for the home's AGENTS.md; the fm-spawn launch reaches the input prompt with only the project's AGENTS.md loaded. Two edge-case home paths went through the real fm-spawn with a stub tmux pane only (no harness started), so those scenarios are recorded as untested live: the glob-character path printed a warning and left the settings unchanged, and the apostrophe path gave settings the shell parses as valid JSON. The Herdr lab could not be used because bin/fm-herdr-lab.sh requires a running default session for its fleet-state tripwire, and the default session was stopped; it was left untouched. Temporary directories were removed, the worktree is clean, and Herdr sessions are unchanged. Overall result: go.

  • Live validation: ✅ go - 6 of 9 scenarios driven live against the product
Scenario Result Live Evidence
A Claude worker nested in a firstmate home sends a first request without the home's supervisor contract but with the project's own rules ✅ pass live live-e2e.log (claude 2.1.281 ok) and realistic-nested-home.log: with the fm-spawn launch, home_contract_hits drops from 1 to 0, project_rules_hits stays 1, and the request shrinks from 174654 to 11309…
A Pi worker nested in a firstmate home sends a first request without the home's contract but with the project's own rules ✅ pass live live-e2e.log (pi 0.85.1 ok) and realistic-nested-home.log: with the fm-spawn extension, the request goes from 67908 to 7099 bytes, the contract is gone, and project rules remain
Firstmate internal skill names do not appear in a nested Claude worker's request ✅ pass live realistic-nested-home.log: firstmate_skill_names_found=0/3 with the fm-spawn launch (the 3/3 baseline hits come from AGENTS.md mentioning those skills)
An interactive nested Claude worker no longer gets the external CLAUDE.md import consent prompt ✅ pass live tui-baseline.txt shows 'Allow external CLAUDE.md file imports?' naming the home's AGENTS.md; tui-fmspawn.txt reaches the input prompt with only the project's AGENTS.md loaded (driven in a private tmux…
A worker whose worktree is outside the home launches unchanged and still loads the project's rules ✅ pass live live-e2e.log outside-worktree cases for claude and pi; spawn-nested-behavior.log confirms the claude settings and pi extension are unchanged
A session whose cwd is the home itself (a second mate) keeps loading its supervisor contract ✅ pass live live-e2e.log home-cwd case expects and finds the home contract for claude and pi; spawn-nested-behavior.log shows a secondmate launch carries no claudeMdExcludes
Edge case: a home path containing a glob character warns and falls back to the unchanged launch instead of writing a broken exclusion ⏸️ untested no The prior payload did not establish a live result: only fm-spawn's launch output was checked through a stub tmux pane, and no harness process was started for this path. To test it live, start claude f…
Edge case: a home path containing an apostrophe still produces a correctly quoted, valid --settings argument ⏸️ untested no The prior payload did not establish a live result: only fm-spawn's launch output was checked through a stub tmux pane, and no harness process was started for this path. To test it live, start claude f…
A full fm-spawn of a nested Claude worker into a real Herdr pane starts without the consent prompt ⏸️ untested no The Herdr lab helper (bin/fm-herdr-lab.sh prepare) refused with 'fleet-state tripwire requires exactly one running default session' because the default Herdr session was stopped on this host, and star…
Evidence: Live guard transcript (real claude + pi)

Source: Live guard transcript (real claude + pi)

ok - claude 2.1.281 (Claude Code): a nested worker's first request drops the home contract, keeps the project's, and a home session keeps its own
ok - pi 0.85.1: a nested worker's first request drops the home contract, keeps the project's, and a home session keeps its own
# fm-nested-home-context live guard checked 2 harness(es)
Evidence: Realistic real-home measurement

Source: Realistic real-home measurement

claude nested, pre-change launch request_bytes= 174654 home_contract_hits=1 project_rules_hits=1 firstmate_skill_names_found=3/3 claude nested, fm-spawn launch request_bytes= 113090 home_contract_hits=0 project_rules_hits=1 firstmate_skill_names_found=0/3 pi nested, no filter request_bytes= 67908 home_contract_hits=1 project_rules_hits=1 firstmate_skill_names_found=3/3 pi nested, fm-spawn extension request_bytes= 7099 home_contract_hits=0 project_rules_hits=1 firstmate_skill_names_found=0/3

== fm-spawn claude --settings for nested worker (home=/tmp/fm-nested-real.sUbBVQ/home):
{
  "feedbackDrafts": "off",
  "attribution": {
    "commit": "",
    "pr": "",
    "sessionUrl": false
  },
  "claudeMdExcludes": [
    "/tmp/fm-nested-real.sUbBVQ/home/CLAUDE.md",
    "/tmp/fm-nested-real.sUbBVQ/home/CLAUDE.local.md",
    "/tmp/fm-nested-real.sUbBVQ/home/AGENTS.md",
    "/tmp/fm-nested-real.sUbBVQ/home/.claude/CLAUDE.md",
    "/tmp/fm-nested-real.sUbBVQ/home/.claude/rules/**"
  ]
}
== first model request from a worker nested in the home (2.1.281 (Claude Code); pi 0.85.1)
claude nested, pre-change launch   request_bytes= 174654  home_contract_hits=1  project_rules_hits=1  firstmate_skill_names_found=3/3
claude nested, fm-spawn launch     request_bytes= 113090  home_contract_hits=0  project_rules_hits=1  firstmate_skill_names_found=0/3
pi nested, no filter               request_bytes=  67908  home_contract_hits=1  project_rules_hits=1  firstmate_skill_names_found=3/3
pi nested, fm-spawn extension      request_bytes=   7099  home_contract_hits=0  project_rules_hits=1  firstmate_skill_names_found=0/3
Evidence: Measurement script

Source: Measurement script

#!/usr/bin/env bash
# Realistic nested-home measurement: a throwaway copy of this firstmate repo at
# the change head acts as the home (real AGENTS.md, CLAUDE.md, .claude/skills),
# its own bin/fm-spawn.sh generates the launch for a worker nested at
# <home>/projects/proj/.treehouse/pool/1/proj, and the installed claude and pi
# send their first request to a local capture server (no provider tokens).
set -u
WT=${1:?worktree}
T=$(mktemp -d /tmp/fm-nested-real.XXXXXX); T=$(cd "$T" && pwd -P)
trap 'rm -rf "$T"' EXIT
HOME_DIR="$T/home"; mkdir -p "$HOME_DIR"
git -C "$WT" archive HEAD | tar -x -C "$HOME_DIR"
ROOT=$HOME_DIR
. "$WT/tests/fixtures.sh"
fm_test_spawn_home "$HOME_DIR" claude
PROJ="$HOME_DIR/projects/proj"; NESTED="$PROJ/.treehouse/pool/1/proj"
fm_git_init_commit "$PROJ"
printf 'PROJECT_MARKER project rules\n' > "$PROJ/AGENTS.md"; printf '@AGENTS.md\n' > "$PROJ/CLAUDE.md"
printf '.treehouse/\n' > "$PROJ/.gitignore"
git -C "$PROJ" add -A && git -C "$PROJ" -c user.name=t -c user.email=t@t commit -qm p
fm_git_add_origin "$PROJ" "$PROJ.origin.git"
mkdir -p "$(dirname "$NESTED")"; git -C "$PROJ" worktree add -q -b wt "$NESTED"
FAKE=$(fm_test_make_spawn_fakebin "$T/fake")
cat > "$FAKE/pi" <<'SH'
#!/usr/bin/env bash
[ "${1:-}" = --help ] && printf '%s\n' 'Pi 0.85.1' 'Options: --help --tui-mode <mode>'
exit 0
SH
chmod +x "$FAKE/pi"
spawn() { fm_test_spawn_brief "$HOME_DIR" "$1"; printf '%s\n' "$2" > "$HOME_DIR/config/crew-harness"; : > "$T/$1.log"
  FM_FAKE_LAUNCH_LOG="$T/$1.log" fm_test_run_spawn "$HOME_DIR" "$NESTED" "$FAKE" "$1" "$PROJ" --harness "$2" --mode no-mistakes --yolo off >/dev/null || { echo "spawn $2 failed"; exit 1; }; }
spawn real-claude claude
spawn real-pi pi
SETTINGS=$(sed -n "s/.* --settings '\([^']*\)'.*/\1/p" "$T/real-claude.log" | head -n1)
echo "== fm-spawn claude --settings for nested worker (home=$HOME_DIR):"; printf '%s' "$SETTINGS" | jq .
PY="$T/cap.py"; cat > "$PY" <<'PY'
import http.server, itertools, json, os, sys
out=sys.argv[1]; seq=itertools.count()
class H(http.server.BaseHTTPRequestHandler):
    def do_POST(self):
        b=self.rfile.read(int(self.headers.get("content-length") or 0))
        open(os.path.join(out,"%03d.json"%next(seq)),"wb").write(b)
        r=json.dumps({"type":"error","error":{"type":"invalid_request_error","message":"capture"}}).encode()
        self.send_response(400); self.send_header("content-type","application/json"); self.send_header("content-length",str(len(r))); self.end_headers(); self.wfile.write(r)
    def do_GET(self): self.send_response(404); self.send_header("content-length","0"); self.end_headers()
    def log_message(self,*a): pass
s=http.server.ThreadingHTTPServer(("127.0.0.1",0),H); open(os.path.join(out,"port"),"w").write(str(s.server_address[1])); s.serve_forever()
PY
CFG="$T/claude-config"; mkdir -p "$CFG"
jq -n --arg p "$PROJ" --arg n "$NESTED" --arg h "$HOME_DIR" '{hasTrustDialogAccepted:true,hasClaudeMdExternalIncludesApproved:true,hasClaudeMdExternalIncludesWarningShown:true} as $e | {projects:{($p):$e,($n):$e,($h):$e}}' > "$CFG/.claude.json"
PIDIR="$T/pi-agent"; mkdir -p "$PIDIR"
cap() { local label=$1 out="$T/cap/$1" pid port i=0; shift; mkdir -p "$out"; python3 "$PY" "$out" & pid=$!
  while [ ! -s "$out/port" ] && [ $i -lt 100 ]; do sleep 0.05; i=$((i+1)); done; port=$(cat "$out/port")
  (cd "$NESTED" && PORT=$port timeout 90 "$@" >/dev/null 2>"$out/err" </dev/null) || true; kill $pid; wait $pid 2>/dev/null
  f=$(grep -l '"messages"' "$out"/[0-9]*.json | head -n1); [ -n "$f" ] || { echo "$label: no request"; return; }
  python3 - "$f" "$label" <<'PY'
import json,sys
b=json.load(open(sys.argv[1])); s=json.dumps(b)
contract=s.count("This is the supervisor contract for primary firstmates")
skills=sum(1 for k in ("bootstrap-diagnostics","captain-hold-lifecycle","stuck-crewmate-recovery") if k in s)
print(f"{sys.argv[2]:<34} request_bytes={len(s.encode()):>7}  home_contract_hits={contract}  project_rules_hits={s.count('PROJECT_MARKER')}  firstmate_skill_names_found={skills}/3")
PY
}
crun() { env -u CLAUDECODE -u CLAUDE_CODE_ENTRYPOINT CLAUDE_CONFIG_DIR="$CFG" ANTHROPIC_API_KEY=sk-capture ANTHROPIC_BASE_URL="http://127.0.0.1:$PORT" CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1 claude -p hi --max-turns 1 --model claude-haiku-4-5 "$@"; }
prun() { printf '{"providers":{"cap":{"baseUrl":"http://127.0.0.1:%s/v1","api":"openai-completions","apiKey":"x","models":[{"id":"m"}]}}}\n' "$PORT" > "$PIDIR/models.json"; PI_CODING_AGENT_DIR="$PIDIR" PI_OFFLINE=1 pi -p --no-session --provider cap --model m "$@" hi; }
export -f crun prun; export CFG PIDIR
echo "== first model request from a worker nested in the home ($(claude --version | head -n1); pi $(pi --version))"
cap "claude nested, pre-change launch" bash -c 'crun'
cap "claude nested, fm-spawn launch" bash -c 'crun --settings "$1"' _ "$SETTINGS"
cap "pi nested, no filter" bash -c 'prun'
cap "pi nested, fm-spawn extension" bash -c 'prun -e "$1"' _ "$HOME_DIR/state/real-pi.pi-ext.ts"
Evidence: Claude screen, baseline launch: external-import consent dialog

Source: Claude screen, baseline launch: external-import consent dialog

$ cd /tmp/fm-lab-nested.mbhxQR/home/projects/proj/.treehouse/pool/1/proj
$ claude --dangerously-skip-permissions


────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  Allow external CLAUDE.md file imports?

  This project's CLAUDE.md imports files outside the current working directory. Never allow this for third-party
  repositories.

  External imports:
    /tmp/fm-lab-nested.mbhxQR/home/AGENTS.md

  Important: Only use Claude Code with files you trust. Accessing untrusted files may pose security risks
  https://code.claude.com/docs/en/security

  ❯ No, disable external imports
    Yes, allow external imports

  Enter to confirm · Esc to cancel
Evidence: Claude screen, fm-spawn launch: straight to prompt, project AGENTS.md only

Source: Claude screen, fm-spawn launch: straight to prompt, project AGENTS.md only

$ cd /tmp/fm-lab-nested.mbhxQR/home/projects/proj/.treehouse/pool/1/proj
$ claude --dangerously-skip-permissions --settings '{"feedbackDrafts":"off","attribution":{"commit":"","pr":"","sessionUrl":false},"claudeMdExcludes":["/tmp/fm-lab-nested.mbhxQR/home/CLAUDE.md","/tmp/fm-lab-nested.mbhxQR/home/CLAUDE.local.md","/tmp/fm-lab-nested.mbhxQR/home/AGENTS.md","/tmp/fm-lab-nested.mbhxQR/home/.claude/CLAUDE.md","/tmp/fm-lab-nested.mbhxQR/home/.claude/rules/**"]}'


 ▐▛███▛█   Claude Code v2.1.281
▝▜██████▀  Opus 5.5 (1M context) · API Usage Billing
  ▝▝ ▝▝    /tmp/fm-lab-nested.mbhxQR/home/projects/proj/.treehouse/pool/1/proj


● agents-md: no CLAUDE.md found; AGENTS.md loaded:
  /tmp/fm-lab-nested.mbhxQR/home/projects/proj/.treehouse/pool/1/proj/AGENTS.md

















                     tmux detected · scroll with PgUp/PgDn · or add 'set -g mouse on' to ~/.tmux.conf for wheel scroll
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
❯ 
────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
  ⏵⏵ bypass permissions on (shift+tab to cycle) · ← for agents
Evidence: Edge-case home paths (glob char, apostrophe)

Source: Edge-case home paths (glob char, apostrophe)

== glob metacharacter (home=/tmp/fm-nested-adv.LERR7b/home[1]) spawn exit=0
warning: firstmate home /tmp/fm-nested-adv.LERR7b/home[1] contains a glob character, so this nested claude worker still loads its instruction files
settings arg as the shell parses it: {"feedbackDrafts":"off","attribution":{"commit":"","pr":"","sessionUrl":false}}
{"valid_json":true,"claudeMdExcludes":null}
== apostrophe (home=/tmp/fm-nested-adv.LERR7b/cap'n home) spawn exit=0
(no glob warning)
settings arg as the shell parses it: {"feedbackDrafts":"off","attribution":{"commit":"","pr":"","sessionUrl":false},"claudeMdExcludes":["/tmp/fm-nested-adv.LERR7b/cap'n home/CLAUDE.md","/tmp/fm-nested-adv.LERR7b/cap'n home/CLAUDE.local.md","/tmp/fm-nested-adv.LERR7b/cap'n home/AGENTS.md","/tmp/fm-nested-adv.LERR7b/cap'n home/.claude/CLAUDE.md","/tmp/fm-nested-adv.LERR7b/cap'n home/.claude/rules/**"]}
{"valid_json":true,"claudeMdExcludes":["/tmp/fm-nested-adv.LERR7b/cap'n home/CLAUDE.md","/tmp/fm-nested-adv.LERR7b/cap'n home/CLAUDE.local.md","/tmp/fm-nested-adv.LERR7b/cap'n home/AGENTS.md","/tmp/fm-nested-adv.LERR7b/cap'n home/.claude/CLAUDE.md","/tmp/fm-nested-adv.LERR7b/cap'n home/.claude/rules/**"]}
Evidence: fm-spawn behavior test output

Source: fm-spawn behavior test output

ok - a nested claude worker's launch excludes only the home's own instruction files
ok - a claude worker whose worktree is outside the home launches unchanged
ok - a second mate's launch excludes no instruction file, so its home contract still loads
ok - a nested pi worker's extension drops the home context file and keeps the project's
ok - a pi worker whose worktree is outside the home registers no context filter
# all fm-spawn-nested-home-context tests passed

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ℹ️ bin/fm-spawn.sh:4287 - This is a leftover gap, and it was already there before this change. The recorded Pi evidence (docs/verification/runtime-backends.md, Pi section) says Pi loads an instruction file from every directory between the cwd and the filesystem root. Claude, by contrast, skips the primary checkout's directories. So a nested Pi worker at <home>/projects/<project>/.treehouse/<pool>/<n>/<project> still loads the primary clone's <home>/projects/<project>/AGENTS.md as well as its worktree's own copy. That copy is the project's rules, not the firstmate supervisor contract the intent names, but it is a second copy and may come from another branch. The new filter only drops files sitting directly in the home, which matches the intent, so no change is needed here. It may be worth a follow-up if nested Pi first-call sizes still sit above the outside-home baseline.
✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 6 of 9 scenarios driven live against the product
Scenario Result Live Evidence
A Claude worker nested in a firstmate home sends a first request without the home's supervisor contract but with the project's own rules ✅ pass live live-e2e.log (claude 2.1.281 ok) and realistic-nested-home.log: with the fm-spawn launch, home_contract_hits drops from 1 to 0, project_rules_hits stays 1, and the request shrinks from 174654 to 11309…
A Pi worker nested in a firstmate home sends a first request without the home's contract but with the project's own rules ✅ pass live live-e2e.log (pi 0.85.1 ok) and realistic-nested-home.log: with the fm-spawn extension, the request goes from 67908 to 7099 bytes, the contract is gone, and project rules remain
Firstmate internal skill names do not appear in a nested Claude worker's request ✅ pass live realistic-nested-home.log: firstmate_skill_names_found=0/3 with the fm-spawn launch (the 3/3 baseline hits come from AGENTS.md mentioning those skills)
An interactive nested Claude worker no longer gets the external CLAUDE.md import consent prompt ✅ pass live tui-baseline.txt shows 'Allow external CLAUDE.md file imports?' naming the home's AGENTS.md; tui-fmspawn.txt reaches the input prompt with only the project's AGENTS.md loaded (driven in a private tmux…
A worker whose worktree is outside the home launches unchanged and still loads the project's rules ✅ pass live live-e2e.log outside-worktree cases for claude and pi; spawn-nested-behavior.log confirms the claude settings and pi extension are unchanged
A session whose cwd is the home itself (a second mate) keeps loading its supervisor contract ✅ pass live live-e2e.log home-cwd case expects and finds the home contract for claude and pi; spawn-nested-behavior.log shows a secondmate launch carries no claudeMdExcludes
Edge case: a home path containing a glob character warns and falls back to the unchanged launch instead of writing a broken exclusion ⏸️ untested no The prior payload did not establish a live result: only fm-spawn's launch output was checked through a stub tmux pane, and no harness process was started for this path. To test it live, start claude f…
Edge case: a home path containing an apostrophe still produces a correctly quoted, valid --settings argument ⏸️ untested no The prior payload did not establish a live result: only fm-spawn's launch output was checked through a stub tmux pane, and no harness process was started for this path. To test it live, start claude f…
A full fm-spawn of a nested Claude worker into a real Herdr pane starts without the consent prompt ⏸️ untested no The Herdr lab helper (bin/fm-herdr-lab.sh prepare) refused with 'fleet-state tripwire requires exactly one running default session' because the default Herdr session was stopped on this host, and star…
  • FM_NESTED_HOME_CONTEXT_LIVE=1 bash tests/fm-nested-home-context-live-e2e.test.sh (real claude 2.1.281 + pi 0.85.1 against a capture server)
  • bash tests/fm-spawn-nested-home-context.test.sh
  • realistic-nested-home.sh &lt;worktree&gt;: real-repo home copy, real fm-spawn launch, first-request size and contract/skill/project-rule presence for claude and pi, baseline vs fm-spawn
  • Interactive claude --dangerously-skip-permissions in the nested worktree inside a private tmux -L server, with and without fm-spawn's --settings (isolated CLAUDE_CONFIG_DIR, worktree trusted, external imports not approved), screen captured after 12s
  • adversarial-home-paths.sh &lt;worktree&gt;: real fm-spawn for a nested claude worker whose home path contains [1] and then an apostrophe; launch parsed by the shell and checked with jq
  • Attempted bin/fm-herdr-lab.sh prepare/provision fm-lab-nestedctx-*; refused because the default session was not running
✅ **Document** - passed

✅ No issues found.

⚠️ **Lint** - 1 warning
  • ⚠️ linter found issues (exit code 1)
✅ **Push** - passed

✅ No issues found.

…tructions

A ship or scout worktree inside a firstmate home, as a second mate's
in-project Treehouse pool places it, loaded the home's CLAUDE.md and
@AGENTS.md supervisor contract on every Claude and Pi request. A nested
Claude launch now carries claudeMdExcludes for the home's instruction
files in its per-launch --settings, and a nested Pi launch's per-task
extension removes the home's context-file blocks from the system prompt.
Second mates and workers outside every home launch unchanged.

Adds a portable spawn test, an opt-out live guard against the installed
harnesses, and dated per-harness verification, including Cursor and
Oh My Pi as documented unfixed leaks.
@Oskari-Heikkinen Oskari-Heikkinen changed the title fix(bin): keep nested workers from loading their firstmate home's instructions fix(bin): keep the firstmate home's supervisor contract out of nested ship and scout workers Sep 24, 2026
@Oskari-Heikkinen
Oskari-Heikkinen merged commit 2547c2e into main Sep 24, 2026
20 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