Skip to content

fix(bin): derive agy trust's project from the pooled worktree - #5069

Open
Xelanor wants to merge 2 commits into
kunchenguid:mainfrom
Xelanor:fm/fm-agy-pooled-worktree-trust
Open

Xelanor wants to merge 2 commits into
kunchenguid:mainfrom
Xelanor:fm/fm-agy-pooled-worktree-trust

Conversation

@Xelanor

@Xelanor Xelanor commented Sep 20, 2026

Copy link
Copy Markdown

Intent

Follow-up raised during the review of fm-pooled-worktree-claude-trust, which fixed the same class of problem for claude.

bin/fm-spawn.sh:3967 still passes the spawning home's registered project to bin/fm-agy-trust.sh. So an agy spawn into a worktree that belongs to a different clone - the shared-pool case that fix addressed - hits the same structural scope mismatch. It is not fatal for agy today: it warns and falls back to the post-launch dialog gate, so the worker still reaches its brief.

docs/verification/agy.md:61 describes that path as asserting "the spawning project". That stays accurate for agy as written, but it now diverges from how claude derives the same value.

Firstmate's decision on that review finding was to leave both the code and the doc line untouched and carry this as its own item, because extending the derivation to agy is a decision about agy behaviour rather than a correction the claude fix required.

This task is also the first real piece of work dispatched on the Z.AI GLM-5.3 runtime, which the captain added to the fleet today. Work normally; that is a fleet matter, not a change to this task.

What Changed

  • bin/fm-spawn.sh now derives the <project> argument it passes to bin/fm-agy-trust.sh from the acquired worktree's own git common dir instead of the spawning home's registered project, so a pooled worktree linked to another clone of the same origin passes the helper's structural scope test and gets pre-registered. The registered project stays the fallback when the common dir cannot be resolved, and the scope test itself is unchanged.
  • tests/fm-agy-harness.test.sh gains two cases: a worktree linked to a sibling clone of the project's own origin is pre-trusted through its real primary checkout with only the launch Enter, and a sibling clone's primary checkout that the same derivation names is still refused, leaves the store untouched, and takes the warn-and-answer dialog fallback.
  • docs/verification/agy.md and the agy harness adapter reference now describe the derivation, its fallback, and the new test coverage, replacing the earlier "spawning project" wording.

Risk Assessment

✅ Low: The change is a bounded, single-arm derivation that mirrors an already-accepted fix, cannot loosen the trust helper's structural scope test, degrades only to the pre-existing warn-and-dialog-gate path on any resolution failure, and is covered by a behavioral regression test that reproduces the reported shape.

Testing

I derived the scenarios from the intent (an agy spawn into a pool worktree owned by another clone of the same origin must pre-register trust instead of falling back to the post-launch dialog) and drove them through the real bin/fm-spawn.sh CLI with the suite's fake tmux and fake agy pane. The pooled spawn now writes the worktree into agy's own settings.json, prints no pre-registration warning, and sends only the launch Enter. Restoring bin/fm-spawn.sh to the base commit makes the same scenario fail with the old scope refusal, so the regression is real and the fix closes it. I added an adversarial scenario: a sibling clone's primary checkout passes the spawn's isolation guard, so I checked the derivation cannot turn it into a trusted folder - the helper still refuses, the store is untouched, and the spawn takes the bounded dialog fallback. A mutation that removes that refusal makes the new case fail, so it is not vacuous. The live vendor path (a real agy 1.2.0 launch in an fm-herdr-lab session) could not be driven: agy is not installed on this host. There is no UI surface in this change, so evidence is CLI output and the persisted trust store rather than screenshots.

  • Live validation: ✅ go - 5 of 6 scenarios driven live against the product
Scenario Result Live Evidence
An agy spawn into a worktree pooled from another clone of the same origin pre-registers trust and launches with no dialog ✅ pass live bin/fm-test-run.sh tests/fm-agy-harness.test.sh drives the real fm-spawn.sh; evidence file agy-pooled-spawn.txt shows the worktree added to agy's settings.json, no pre-registration warning, and 1 En…
Before the fix the same pooled spawn fails pre-registration and falls back to the dialog ✅ pass live Base bin/fm-spawn.sh restored from a09090d: not ok - a worktree pooled against a sibling clone failed trust pre-registration, with the scope refusal and warning in agy-pooled-regression-before-after…
A same-clone worktree still pre-registers exactly as before (no behaviour change on the normal shape) ✅ pass live test_agy_fresh_worktree_is_pre_trusted_and_launches_without_a_dialog passes: store holds the worktree, unrelated keys preserved, 1 Enter send
The derivation cannot pre-trust a sibling clone's primary checkout ✅ pass live New test_agy_spawn_refuses_to_pre_trust_a_sibling_primary_checkout; agy-sibling-primary-refused.txt shows the helper's primary-checkout refusal, the store left unchanged, and the bounded dialog fall…
A path with no resolvable git common dir still falls back to the registered project and its existing failure handling ✅ pass live test_agy_unregistered_path_ignores_busy_until_the_dialog_is_answered and test_agy_unregistered_path_without_a_dialog_fails_the_spawn pass: the warning path, the unparseable store left untouched, a…
A real agy 1.2.0 worker launched into a pooled worktree reaches its brief with no folder-trust dialog ⏸️ untested no Needs the Antigravity CLI (agy 1.2.0) installed plus a Gemini credential, driven through bin/fm-herdr-lab.sh in a named fm-lab-* session. Install agy and provide its auth on the test host to drive thi…
Evidence: Pooled spawn: derived project, spawn output, agy trust store after the spawn

Source: Pooled spawn: derived project, spawn output, agy trust store after the spawn

# pooled shape (worktree linked to a sibling clone of the same origin)
spawning home's registered project : /tmp/fm-agy-harness.KpDGr5/pooled/project
acquired pool worktree             : /tmp/fm-agy-harness.KpDGr5/pooled/wt
worktree's real primary checkout   : /tmp/fm-agy-harness.KpDGr5/pooled/other-clone
same origin on both clones         : /tmp/fm-agy-harness.KpDGr5/pooled/other-clone.origin.git | file:///tmp/fm-agy-harness.KpDGr5/pooled/other-clone.origin.git

# spawn output
warning: /tmp/fm-agy-harness.KpDGr5/pooled/home/data/agy-pooled-z15-31092/launch-brief.md records no delivery contract line (scaffolded before ship briefs recorded one); launching on the explicit --mode no-mistakes - confirm its definition of done matches
spawned agy-pooled-z15-31092 harness=agy kind=ship mode=no-mistakes yolo=off window=firstmate:fm-agy-pooled-z15-31092 worktree=/tmp/fm-agy-harness.KpDGr5/pooled/wt

# agy trust store after the spawn (HOME/.gemini/antigravity-cli/settings.json)
{
  "model": "Gemini 3.8 Flash (High)",
  "trustedWorkspaces": [
    "~/elsewhere",
    "/tmp/fm-agy-harness.KpDGr5/pooled/wt"
  ]
}
# Enter key sends into the pane (launch only, no dialog answer): 1
Evidence: Adversarial: a sibling clone's primary checkout is refused and never trusted

Source: Adversarial: a sibling clone's primary checkout is refused and never trusted

# adversarial shape: acquired path is a SIBLING CLONE'S PRIMARY CHECKOUT
acquired path : /tmp/fm-agy-harness.KpDGr5/sibling-primary/sibling-clone

# spawn output
warning: /tmp/fm-agy-harness.KpDGr5/sibling-primary/home/data/agy-sibling-primary-z16-31092/launch-brief.md records no delivery contract line (scaffolded before ship briefs recorded one); launching on the explicit --mode no-mistakes - confirm its definition of done matches
error: refusing to pre-register agy trust: '/tmp/fm-agy-harness.KpDGr5/sibling-primary/sibling-clone' is a primary checkout, not an isolated worktree
warning: could not pre-register agy workspace trust for /tmp/fm-agy-harness.KpDGr5/sibling-primary/sibling-clone; the launch will answer the folder-trust dialog in window firstmate:fm-agy-sibling-primary-z16-31092 instead
spawned agy-sibling-primary-z16-31092 harness=agy kind=ship mode=no-mistakes yolo=off window=firstmate:fm-agy-sibling-primary-z16-31092 worktree=/tmp/fm-agy-harness.KpDGr5/sibling-primary/sibling-clone

# agy trust store after the spawn - the clone must NOT be listed
{"model":"Gemini 3.8 Flash (High)","trustedWorkspaces":["~/elsewhere"]}
# Enter key sends (launch + one dialog answer): 2
Evidence: Before/after regression transcript plus the mutation check

Source: Before/after regression transcript plus the mutation check

Regression check for the pooled-worktree agy trust derivation.
Same test, same fixture; only bin/fm-spawn.sh differs.

--- BEFORE the fix (bin/fm-spawn.sh restored to base a09090d) ---
$ bin/fm-test-run.sh tests/fm-agy-harness.test.sh
not ok - a worktree pooled against a sibling clone failed trust pre-registration (unexpected: 'could not pre-register')
error: refusing to pre-register agy trust: '<tmp>/pooled/wt' is not a worktree of project '<tmp>/pooled/project'
warning: could not pre-register agy workspace trust for <tmp>/pooled/wt; the launch will answer the folder-trust dialog in window firstmate:fm-agy-pooled-z15-91905 instead
spawned agy-pooled-z15-91905 harness=agy kind=ship ... worktree=<tmp>/pooled/wt
FM_TEST_SUMMARY total=1 failed=1

--- AFTER the fix (target 51cf453) ---
$ bin/fm-test-run.sh tests/fm-agy-harness.test.sh
ok - fm-spawn: agy pre-trusts a worktree pooled against a sibling clone of its project's own origin
ok - fm-spawn: agy never pre-trusts a sibling clone's primary checkout the derivation names
FM_TEST_SUMMARY total=1 failed=0 skipped_gate=0

--- Mutation check that the new adversarial case is not vacuous ---
With bin/fm-agy-trust.sh's primary-checkout refusal removed, the spawn pre-registered
a whole sibling clone and the adversarial case failed:
not ok - a refused primary checkout did not surface the registration warning
(both the helper and the test file were restored afterwards)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 info
  • ℹ️ bin/fm-spawn.sh:3965 - The derivation block added to the agy arm is a near-verbatim copy of the one the claude pooled-worktree fix (036f30e, branch fm/fm-pooled-worktree-claude-trust) adds to the claude arm of the same case statement. That branch is not in this base, so no duplication exists in this tree today; once both land, the same rule lives twice, ten lines apart, and must be kept in sync. No action needed now - noting it so a later cleanup can lift the derivation into one helper (for example next to spawn_worktree_isolated) rather than editing two copies.
✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 5 of 6 scenarios driven live against the product
Scenario Result Live Evidence
An agy spawn into a worktree pooled from another clone of the same origin pre-registers trust and launches with no dialog ✅ pass live bin/fm-test-run.sh tests/fm-agy-harness.test.sh drives the real fm-spawn.sh; evidence file agy-pooled-spawn.txt shows the worktree added to agy's settings.json, no pre-registration warning, and 1 En…
Before the fix the same pooled spawn fails pre-registration and falls back to the dialog ✅ pass live Base bin/fm-spawn.sh restored from a09090d: not ok - a worktree pooled against a sibling clone failed trust pre-registration, with the scope refusal and warning in agy-pooled-regression-before-after…
A same-clone worktree still pre-registers exactly as before (no behaviour change on the normal shape) ✅ pass live test_agy_fresh_worktree_is_pre_trusted_and_launches_without_a_dialog passes: store holds the worktree, unrelated keys preserved, 1 Enter send
The derivation cannot pre-trust a sibling clone's primary checkout ✅ pass live New test_agy_spawn_refuses_to_pre_trust_a_sibling_primary_checkout; agy-sibling-primary-refused.txt shows the helper's primary-checkout refusal, the store left unchanged, and the bounded dialog fall…
A path with no resolvable git common dir still falls back to the registered project and its existing failure handling ✅ pass live test_agy_unregistered_path_ignores_busy_until_the_dialog_is_answered and test_agy_unregistered_path_without_a_dialog_fails_the_spawn pass: the warning path, the unparseable store left untouched, a…
A real agy 1.2.0 worker launched into a pooled worktree reaches its brief with no folder-trust dialog ⏸️ untested no Needs the Antigravity CLI (agy 1.2.0) installed plus a Gemini credential, driven through bin/fm-herdr-lab.sh in a named fm-lab-* session. Install agy and provide its auth on the test host to drive thi…
  • bin/fm-test-run.sh tests/fm-agy-harness.test.sh (31 cases, all pass on the target commit)
  • Base-commit regression: restored bin/fm-spawn.sh from a09090d and re-ran the same suite; the pooled case fails with error: refusing to pre-register agy trust: ... is not a worktree of project ...
  • New adversarial case test_agy_spawn_refuses_to_pre_trust_a_sibling_primary_checkout driving fm-spawn.sh with a sibling clone's primary checkout as the acquired path
  • Mutation check: removed the primary-checkout refusal in bin/fm-agy-trust.sh and confirmed the adversarial case then fails (helper restored afterwards)
  • Captured the real agy settings.json state and the spawn CLI output for both the pooled and the refused shapes
⚠️ **Document** - 1 info
  • ℹ️ docs/verification/agy.md:75 - Judgment call, applied: the new evidence block in docs/verification/agy.md quoted a one-off PID-derived window name (firstmate:fm-agy-pooled-z15-70118). docs/documentation-audiences.md says one-off process identifiers stay in private task reports by default, while maintainer-verification pages may carry exact output. The surrounding block was already normalized with <tmp> placeholders, so I redacted the identifier to <id> for consistency rather than leaving a literal PID in a tracked page. If the reviewer prefers byte-exact captured output on verification pages, revert that one token.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

A Treehouse pool is keyed by a project's resolved origin and shared by
every local clone of that origin, so an agy spawn can be handed a pool
worktree linked to a different home's clone than the project this
spawn's own home registered. fm-spawn.sh passed the registered project
to fm-agy-trust.sh regardless, whose structural scope test correctly
refused the false assertion, so every cross-clone agy spawn fell back
to answering the folder-trust dialog after launch: readiness depended
on the dialog rendering, a single non-retried Enter landing, and a busy
verdict all inside one bounded window, plus a warning per spawn for a
structurally normal fleet shape. The fallback is bounded and cannot
wedge - every gate failure path fails the spawn with endpoint cleanup -
but it is the weaker, dialog-dependent route for a normal shape.

Derive the agy-trust <project> argument from the acquired worktree's
own git common dir instead, falling back to the registered project
only when that cannot be resolved, the same derivation the claude
pooled-worktree fix applies. The trust script's scope test is
unchanged.
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