Skip to content

fix(spawn): stage launch commands in a private per-spawn file and pin COMPACT_ADVISER_DISABLE - #139

Merged
marano merged 1 commit into
mainfrom
fm/fm-port-launch-staging
Sep 24, 2026
Merged

marano merged 1 commit into
mainfrom
fm/fm-port-launch-staging

Conversation

@marano

@marano marano commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Intent

Port two upstream firstmate improvements (upstream repository kunchenguid/firstmate) into this fork as our own changes. This fork is a hard fork: do not merge, rebase onto, or cherry-pick from upstream; read the upstream commits only as reference.

  1. Upstream #4994 (commit a452a79e): long worker launch commands get truncated. A long launch line typed into a fresh pane shell waits in the terminal's canonical line buffer, which drops input past about 1,024 bytes on macOS, leaving the pane at an unfinished command with no agent running. Upstream writes the assembled command to a private per-spawn file under umask 077 and types only a short line that sources it. Here bin/fm-spawn.sh still types the whole command (spawn_send_literal "$T" "$LAUNCH"); our fix(bin): confirm a spawned agent started and clear pending shell input before relaunch #37 (2f8f630) only mitigates the symptom by clearing pending input and retrying. Measured on this home: real launch strings are 951-983 bytes, 41 bytes under the limit; a longer task id, a deeper home path, or creating config/launch-env-allowlist (about 1,200 bytes on its own) crosses it.
  2. Upstream #4877 (commit 1bb72cc5): launch every spawned agent with COMPACT_ADVISER_DISABLE=1, so an unattended worker never activates the compact adviser. Upstream carries it three ways: pane shell export, explicit assignment on the launch command, and the env -i floor. Nothing in bin/ sets it here.

Both edit bin/fm-spawn.sh, so they ship in one pass.

What Changed

The main ports were already delivered before this PR: #95 (bac8c60) stages the launch command in a file and types only a short line sourcing it, and #115 (fdbd6e7) launches every spawned agent with COMPACT_ADVISER_DISABLE=1 through the pane export and the launch command's own export.
This PR closes the remaining gap with upstream kunchenguid/firstmate#4994 and kunchenguid/firstmate#4877:

  • Compact-adviser third carrier (from upstream #4877): under an enabled config/launch-env-allowlist, COMPACT_ADVISER_DISABLE joins the env -i operational floor and a literal COMPACT_ADVISER_DISABLE=1 is pinned last on the env prefix, so the wrapping /bin/sh holds the switch before the launch command's own export and it overrides a forwarded pane value.
  • Private task temp root (from upstream #4994): /tmp/fm-<id> is created with umask 077 (0700). A pre-existing root is reused only as a real directory owned by this user that nobody else can write, and is tightened to 0700; anything else refuses the spawn before any launch, closing the endpoint a fresh spawn created.
  • Never-reused launch file (from upstream #4994): each spawn or relaunch stages launch.<spawn_gen>.sh (0600) and refuses to replace an existing one, so a source line still buffered from an earlier incarnation cannot run a relaunch's command. Teardown already removes the whole temp root.
  • Upstream's home-namespaced launch directory was deliberately left out.
  • The fix(bin): confirm a spawned agent started and clear pending shell input before relaunch #37 recovery (clear pending input, confirm the agent started, retry) is unchanged; none of it is made dead by this change.

Tests, each confirmed to fail by name under its mutant:

  • test_env_boundary_carries_the_switch_on_its_own (dropping the env -i literal reads 0).
  • test_spawn_stages_its_launch_in_a_private_root (root created without umask 077 lands 755).
  • test_spawn_refuses_a_task_temp_root_others_can_write (dropping the writable check launches from an open root; dropping chmod 700 leaves a reused root 755).
  • test_relaunch_never_reuses_a_launch_file (a fixed file name leaves one overwritten file).
  • The refusal to replace an existing launch file is not separately exercised, because the incarnation name is random; the relaunch test covers that no name is reused.

Risk Assessment

✅ Low: The change satisfies both intent items (launch file sourced from a short line, COMPACT_ADVISER_DISABLE carried three ways), the hardening of the temp root is bounded, and the new tests execute the emitted launch rather than grepping source.

Testing

Ran the three spawn tests this diff touches, each end to end against fm-spawn.sh. All passed. The launch-confirm test covers the 4772-byte command typed as lines of at most 86 bytes. Output of the last 25 lines of each run is in tests.log.

  • Live validation: ✅ go - 3 of 3 scenarios driven live against the product
Scenario Result Live Evidence
A 4772-byte launch command reaches the pane as short typed lines (max 86 bytes) via a staged file ✅ pass live tests.log: fm-spawn-launch-confirm 'the longest launch types no line over 86 bytes while the command itself is 4772'
Launch file is staged mode 0600 in a 0700 temp root; unsafe roots refused; relaunch stages a new file ✅ pass live tests.log: fm-spawn-launch-confirm staging and relaunch tests
Agent starts with COMPACT_ADVISER_DISABLE=1 with and without the env allowlist, including secondmate, relaunch and remote routes ✅ pass live tests.log: fm-spawn-compact-adviser-disable and fm-spawn-compact-adviser-disable-remote tests
Evidence: test output

Source: test output

== fm-spawn-launch-confirm
ok - a spawn whose launch never started fails and closes its pane
ok - a spawn stages its launch command 0600 in a 0700 task temp root
ok - a spawn refuses a temp root others can write and tightens one it owns
ok - a relaunch stages a new launch file and leaves the earlier one untouched
ok - a spawn clears a cut launch's continuation prompt and starts the agent
ok - a relaunch clears a continuation prompt before typing its launch
ok - a launch typed while the pane shell runs a slow prompt hook starts the agent
ok - the longest launch types no line over 86 bytes while the command itself is 4772
== fm-spawn-compact-adviser-disable
ok - ship launch with no allowlist starts its agent with the compact-adviser switch on
ok - ship launch under an enabled allowlist keeps the compact-adviser switch through the cleared environment
ok - the launch command sets the switch on its own, whichever allowlist posture is in force
ok - the cleared-environment boundary sets the compact-adviser switch on its own
ok - a secondmate launch carries the compact-adviser switch in both allowlist postures
ok - relaunch rebuilds the compact-adviser switch for the replacement agent in both allowlist postures
ok - a compound raw launch-command still starts its agent with the compact-adviser switch on
== fm-spawn-compact-adviser-disable-remote
ok - a remote-routed second mate starts with the compact adviser disabled, from the pane export and from the launch command alike
ok - the remote route keeps the compact-adviser switch through the cleared allowlisted environment
ALL TESTS PASSED

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 3 of 3 scenarios driven live against the product
Scenario Result Live Evidence
A 4772-byte launch command reaches the pane as short typed lines (max 86 bytes) via a staged file ✅ pass live tests.log: fm-spawn-launch-confirm 'the longest launch types no line over 86 bytes while the command itself is 4772'
Launch file is staged mode 0600 in a 0700 temp root; unsafe roots refused; relaunch stages a new file ✅ pass live tests.log: fm-spawn-launch-confirm staging and relaunch tests
Agent starts with COMPACT_ADVISER_DISABLE=1 with and without the env allowlist, including secondmate, relaunch and remote routes ✅ pass live tests.log: fm-spawn-compact-adviser-disable and fm-spawn-compact-adviser-disable-remote tests
  • bash tests/fm-spawn-launch-confirm.test.sh
  • bash tests/fm-spawn-compact-adviser-disable.test.sh
  • bash tests/fm-spawn-compact-adviser-disable-remote.test.sh
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

…ps with upstream

Stage the launch command in a task temp root created 0700, refusing a
pre-existing root that is not a real directory owned by this user or that
others can write, and tightening one this user owns.

Name each launch file for its spawn incarnation and never reuse or replace
one, so a source line still buffered from an earlier incarnation cannot run a
relaunch's command.

Pin COMPACT_ADVISER_DISABLE=1 at the env -i boundary of the cleared launch
environment, and keep the name in its operational floor, so the wrapping
/bin/sh holds the switch before the launch command's own export.
@marano
marano merged commit 1023912 into main Sep 24, 2026
19 checks passed
@marano
marano deleted the fm/fm-port-launch-staging branch September 24, 2026 18:50
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