Skip to content

fix(spawn): refuse direct-PR where CI requires no-mistakes PRs - #11

Merged
NewAiCoder merged 3 commits into
mainfrom
fm/spawn-refuse-direct-pr-attested
Sep 27, 2026
Merged

NewAiCoder merged 3 commits into
mainfrom
fm/spawn-refuse-direct-pr-attested

Conversation

@NewAiCoder

Copy link
Copy Markdown
Owner

Intent

The captain asked for a mechanical guard so this never happens again: a firstmate fix was dispatched with delivery mode direct-PR to a repository whose CI carries the "PR must be raised via no-mistakes" required check (.github/workflows/no-mistakes-required.yml). The directly opened PR failed that check and the whole change had to be re-run through the no-mistakes pipeline, costing about 30 minutes. The rule was already written in the supervisor's notes and in the project registry prose, but nothing enforced it. The guard: when firstmate dispatches a ship task with --mode direct-PR to a project whose repository requires PRs raised via no-mistakes, the dispatch refuses with a clear message naming the check and telling the supervisor to use --mode no-mistakes, instead of launching a worker whose PR is guaranteed to go red.

What Changed

  • bin/fm-spawn.sh now refuses a --mode direct-PR ship spawn when the project's local checkout has a .github/workflows/*.yml|yaml file containing the PR must be raised via no-mistakes check. It exits with an error naming that workflow and telling the supervisor to use --mode no-mistakes. The check matches on the job name rather than the workflow filename, and needs no network access. resolve_project_dir_arg moved earlier in the script so the guard can use it, including for projects/<name> aliases.
  • tests/fm-task-delivery.test.sh gains coverage for the new refusal.
  • AGENTS.md and docs/architecture.md document the refusal in the delivery-mode guidance.

Risk Assessment

✅ Low: The guard is a small, well-scoped pre-spawn refusal that now resolves the projects/ alias, is covered by a behavioral test that runs the real spawn script, and matches the stated intent.

Testing

I ran tests/fm-task-delivery.test.sh, which passes, including the new test for this guard. I then ran the real bin/fm-spawn.sh from /tmp against this repo, whose real .github/workflows/no-mistakes-required.yml is present. I used an isolated home, and a fake tmux that exits non-zero so nothing is launched. --mode direct-PR was refused (exit 1) with the workflow name and --mode no-mistakes, both by absolute path and by the projects/&lt;name&gt; alias. --mode no-mistakes, --mode local-only, and --mode direct-PR on a project with no workflow all got past the guard and stopped only at the fake tmux. No spawn state was written. The transcript is saved as evidence and the worktree is clean.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
direct-PR spawn to a repo with the no-mistakes-required check (absolute project path) is refused with a message naming the workflow and --mode no-mistakes ✅ pass live live-spawn-transcript.txt case 1: exit=1 with the refusal message
direct-PR spawn using the projects/<name> alias from a different cwd is still refused (regression fix from review round 1) ✅ pass live live-spawn-transcript.txt case 2: exit=1 with the refusal message
--mode no-mistakes to the same project is not blocked by the guard ✅ pass live live-spawn-transcript.txt case 3: no attestation error, stops only at the fake tmux
--mode local-only to the same project is not blocked by the guard ✅ pass live live-spawn-transcript.txt case 4: no attestation error, stops only at the fake tmux
direct-PR to a project without the workflow is not refused (no false positive) ✅ pass live live-spawn-transcript.txt case 5: no attestation error, stops only at the fake tmux
Refused spawn creates no task metadata or state ✅ pass live tests/fm-task-delivery.test.sh asserts no .meta file; the transcript shows the state directory empty
Evidence: Live fm-spawn transcript

Source: Live fm-spawn transcript

--- 1 direct-PR abs path, real repo workflow (cwd=/tmp)
error: this project's .github/workflows/no-mistakes-required.yml requires PRs to be raised via no-mistakes, so a direct-PR pull request is guaranteed to fail that check; spawn with --mode no-mistakes
exit=1
--- 2 direct-PR projects/ alias, cwd=/tmp
error: this project's .github/workflows/no-mistakes-required.yml requires PRs to be raised via no-mistakes, so a direct-PR pull request is guaranteed to fail that check; spawn with --mode no-mistakes
exit=1
--- 3 no-mistakes mode, same project
exit=1
--- 4 local-only mode, same project
notice: t4 ships mode=local-only while the standing posture for 01M3GFKQPP8Z86T427AKY4JX60 is no-mistakes - less rigor than the captain's standing posture; proceed only on a current explicit captain instruction or an intake judgment you can state
exit=1
--- 5 direct-PR, project without workflow
notice: t5 ships mode=direct-PR while the standing posture for plain is no-mistakes - less rigor than the captain's standing posture; proceed only on a current explicit captain instruction or an intake judgment you can state
exit=1
state files:

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 1 issue found → auto-fixed ✅
  • ⚠️ bin/fm-spawn.sh:625 - The guard reads workflows from the raw positional project argument (${POS[1]}), but fm-spawn treats projects/&lt;name&gt; as an alias resolved against $PROJECTS ($FM_HOME/projects) by resolve_project_dir_arg (line ~2125, used at ~2305). With projects/foo and a cwd other than $FM_HOME, nm_attestation_workflow globs a non-existent projects/foo/.github/workflows/*.yml, returns empty, and the guard silently fails open. The spawn then proceeds with --mode direct-PR, and the PR fails the required check, which is the exact incident the change is meant to prevent. Fix: resolve the path the same way, either by moving resolve_project_dir_arg above this block or by inlining its projects/* mapping, before calling nm_attestation_workflow.

🔧 Fix applied.
✅ Re-checked - no issues remain.

✅ **Test** - passed

✅ No issues found.

  • Live validation: ✅ go - 6 of 6 scenarios driven live against the product
Scenario Result Live Evidence
direct-PR spawn to a repo with the no-mistakes-required check (absolute project path) is refused with a message naming the workflow and --mode no-mistakes ✅ pass live live-spawn-transcript.txt case 1: exit=1 with the refusal message
direct-PR spawn using the projects/<name> alias from a different cwd is still refused (regression fix from review round 1) ✅ pass live live-spawn-transcript.txt case 2: exit=1 with the refusal message
--mode no-mistakes to the same project is not blocked by the guard ✅ pass live live-spawn-transcript.txt case 3: no attestation error, stops only at the fake tmux
--mode local-only to the same project is not blocked by the guard ✅ pass live live-spawn-transcript.txt case 4: no attestation error, stops only at the fake tmux
direct-PR to a project without the workflow is not refused (no false positive) ✅ pass live live-spawn-transcript.txt case 5: no attestation error, stops only at the fake tmux
Refused spawn creates no task metadata or state ✅ pass live tests/fm-task-delivery.test.sh asserts no .meta file; the transcript shows the state directory empty
  • bash tests/fm-task-delivery.test.sh (includes the new test_spawn_refuses_direct_pr_where_ci_requires_no_mistakes)
  • Real bin/fm-spawn.sh run from /tmp against this repo's real .github/workflows/no-mistakes-required.yml, in an isolated FM_HOME with a fake tmux that exits non-zero: direct-PR by absolute path, direct-PR via projects/&lt;name&gt; alias, no-mistakes mode, local-only mode, direct-PR on a project with no workflow
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

@NewAiCoder
NewAiCoder merged commit 74d25a4 into main Sep 27, 2026
14 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