Skip to content
This repository was archived by the owner on Aug 25, 2026. It is now read-only.

fix: refine task intake and direct-PR recovery - #80

Merged
JTInventory merged 30 commits into
mainfrom
fm/firstmate-adopt-phase4-934-0726
Jul 27, 2026
Merged

JTInventory merged 30 commits into
mainfrom
fm/firstmate-adopt-phase4-934-0726

Conversation

@JTInventory

Copy link
Copy Markdown
Owner

Intent

Port owner kunchenguid#934 scout intake parallel

What Changed

  • Refine ship/scout intake to reuse established evidence, preserve explicit change authorization, and parallelize overlapping work unless a concrete dependency requires serialization.
  • Add durable, guarded recovery for direct-PR publication and reconciliation, including validated checkpoints, private refs, bounded retries, and crash-safe PR poll replacement.
  • Serialize poll migration, publication, and teardown per task, with expanded hermetic security, crash-recovery, and lock-ordering coverage.

Risk Assessment

✅ Low: Captain, the repair consistently applies WATCH_LOCK-to-task-lock ordering, revalidates after migration, and retains teardown locks through task-state deletion without introducing a material new risk.

Testing

The supplied baseline and final post-fix full behavior runs passed; focused intake, Herdr, teardown, and bounded watcher-lock checks passed, while generated Markdown evidence directly shows scout selection, immediate safe parallel dispatch, and direct-PR conflict ownership.

Evidence: Rendered Firstmate intake contract
### Intake

**Resolve the project first.**
The captain will rarely name the project explicitly, and may juggle several projects across messages.
Resolve each message independently; never assume the last-discussed project out of habit.
Use these signals in order:

1. An explicit project name in the message wins.
2. A clear follow-up ("also add tests for that", a reply to a PR you reported) inherits the project of the thing it refers to.
3. Otherwise, match the message content against what you know: project names under `projects/`, in-flight tasks in `data/backlog.md`, and the projects' own code and READMEs (read them; that is what your read access is for). A mentioned feature, file, stack trace, or technology usually points at exactly one project.
4. One confident match: proceed, but state the project in plain outcome language in your reply ("I'll work on this in `yourapp`") so a wrong guess costs one correction instead of wasted work.
5. More than one plausible match, or none: ask a one-line question. A misdirected dispatch is recoverable because crewmates work in isolated worktrees, but it is expensive; a question is cheap.

Then resolve the secondmate scope.
Read `data/secondmates.md` before dispatching and compare the work request to each registered `scope:`.
Route by the nature of the task, not just the project name.
A project may appear in several `projects:` clone lists, so choose the secondmate whose natural-language scope actually fits the work, such as triage versus feature development.
If the resolved project is `local-only`, keep the work with the main firstmate even when a secondmate scope sounds relevant.
If a secondmate's scope fits, steer that secondmate with one concise instruction via `bin/fm-send.sh fm-<id> '<work request>'` and let it run the normal lifecycle inside its own home.
The only accepted bare target is `fm-<id>`, which resolves through this home's `state/<id>.meta`; other bare window names fail closed, so pass `session:window` only when intentionally targeting a window outside this firstmate home.
A secondmate is itself a firstmate, so a request reaches it in its own chat, which you never read - the return channel that wakes you is its status file.
So `fm-send` to a bare `fm-<id>` whose meta is `kind=secondmate` automatically prepends the terminal-safe U+2063 from-firstmate marker (`bin/fm-marker-lib.sh`) without stripping trailing newlines; the secondmate recognizes it and returns its answer via its status file, or via a doc under its home plus a status pointer for a detailed response, never only in chat.
For codex secondmates, that marked ordinary-text path also uses the longer pre-Enter settle so the already-typed request is not left unsubmitted by input timing.
Expect and read that response on the status/doc path the same way you read any other status signal; do not peek the secondmate's chat for the answer.
A captain typing directly into the secondmate's window is unmarked and stays a conversational captain intervention, so do not relay captain-destined chat through this path; the marker is applied only by `fm-send` to a `kind=secondmate` target.
Do not spawn a direct crewmate for work that belongs to a secondmate scope unless the secondmate is blocked or the captain explicitly redirects it.
If no secondmate scope fits, proceed in the main firstmate or create a new secondmate with the captain when that domain should become persistent.
When you create a new secondmate, hand its in-scope queued items off from the main backlog into its home with `bin/fm-backlog-handoff.sh` so it owns its domain's queue from day one (section 6).

Before commissioning an investigation, consult existing reports and established evidence; then classify the shape:

- **Ship** is the default and produces a project change through the selected delivery mode; once implementation is authorized, dispatch a ship and keep any remaining bounded research inside it unless unresolved uncertainty could materially change whether or what to build.
- **Scout** produces knowledge in `data/<id>/report.md`, never a PR, and is appropriate for investigation, diagnosis, planning, reproduction, or audit work when the captain explicitly requests a separate knowledge or design deliverable or unresolved uncertainty could materially change whether or what to build.

If established evidence already answers an informational question, relay it without a design-only scout; when implementation intent is unclear, answer and ask one concise implementation question when useful rather than dispatching speculative design work; never both present a likely-enough solution and launch a parallel design exercise that is not expected to change it.
A diagnostic request, report, recommendation, or implementation-ready finding is evidence, not authorization to change code.

Then classify readiness:

- Treat file or subsystem overlap as a risk signal rather than an automatic reason to wait, and dispatch isolated work immediately with no concurrency cap when each change can be independently implemented and validated and the selected delivery path can reconcile ordinary rebases or conflicts.
- Serialize only for a true semantic dependency, shared mutable external state, incompatible concurrent migration, or another concrete condition that makes independent progress or reconciliation unsafe; same-file editing alone is insufficient, and genuine blockers remain durable.

Write the brief per section 11.

### Spawn
Evidence: Generated direct-PR crewmate brief
You are a crewmate: an autonomous worker agent managed by firstmate. Work on your own; do not wait for a human.

# Task
{TASK}

# Setup
You are in a disposable git worktree of direct-project, at a detached HEAD on a clean default branch.

**Verify isolation before anything else.** Run `pwd -P` and `git rev-parse --show-toplevel`; both must resolve to the disposable treehouse worktree you were launched in, typically a path under a `.treehouse/` pool, not the primary checkout firstmate operates from.
The path check is authoritative: `git rev-parse --git-dir` and `git rev-parse --git-common-dir` can help inspect the repo, but they do not prove you are outside the primary checkout.
If the top-level path is the primary checkout or not the worktree you were launched in, STOP - do not branch or commit here - append `blocked: launched in primary checkout, not an isolated worktree` to the status file and stop.

1. First action: create your branch: `git checkout -b fm/parallel-direct-pr`

# Rules
1. Never push to the default branch (push only your `fm/parallel-direct-pr` branch). Never merge a PR.
2. Stay inside this worktree except for the status file, the task-specific Firstmate checkpoint at '/tmp/no-mistakes-evidence/01KYG43W7V8H31FFQDVN4AMH6D/fm-home/state/parallel-direct-pr.direct-pr-lease', and its exact stable temporary sibling at '/tmp/no-mistakes-evidence/01KYG43W7V8H31FFQDVN4AMH6D/fm-home/state/parallel-direct-pr.direct-pr-lease.tmp'; modify nothing else outside it.
3. Use gh-axi for GitHub operations and chrome-devtools-axi for browser operations.
4. Report status by appending one line:
   `echo "{state}: {one short line}" >> '/tmp/no-mistakes-evidence/01KYG43W7V8H31FFQDVN4AMH6D/fm-home/state/parallel-direct-pr.status'`
   States: working, needs-decision, blocked, done, failed, plus the exact actionable post-conflict handoff PR ready: {url} checkpoint={checkpoint} task={task} workflow=post-conflict post_head={oid}.
   Each append wakes firstmate, so report sparingly: only phase changes a supervisor
   would act on (setup done, bug reproduced, fix implemented, validation passed) and the
   needs-decision/blocked/done/failed states. No step-by-step FYI progress lines;
   firstmate reads your pane for that.
5. If you hit the same obstacle twice, append `blocked: {why}` and stop; firstmate will help.
6. If a decision belongs to a human (product choices, destructive actions, ask-user findings),
   append `needs-decision: {summary of options}` and stop. Firstmate will reply with the decision.
7. Never stop, restart, or update the shared `no-mistakes` daemon - one instance serves
   every lane/home, so restarting it can kill another lane's in-flight pipeline. On any
   no-mistakes daemon error, append `blocked: {the daemon error}` and stop; only firstmate manages it.

# Cognee memory hints
Cognee is memory/context only. It is not proof, source of truth, durable archive, or action authority.
Official docs expose raw readback and session/model cost surfaces, but Firstmate still treats raw retention/source-authority guarantees and per-wrapper-call cost correlation as unproven.
Do not run automatic Cognee lookup for every task.
Use a Cognee hint only when this brief says all of these are true:
- Firstmate manually performed the lookup.
- The hint maps to a local manifest row or known local report.
- Firstmate reopened and checked the local source path before attaching it.
- The hint is labeled as memory/context only.
- The hint includes stale-risk and says live state still needs verification.
- `external_action_authorized=false`.

Never use raw Cognee answer text as proof.
Never use memory to authorize merge, deploy, cleanup, vendor/customer action, purchase, refresh, import, or deletion.
If a Cognee hint lacks a reopened local source path, ignore it and proceed from repo files, named local reports, live endpoints, GitHub state, service state, and canonical proof artifacts.

# Project memory
If `AGENTS.md` or `CLAUDE.md` already exists, or if this task produced durable project-intrinsic knowledge, run `/root/.no-mistakes/worktrees/7f0ec18181b6/01KYG43W7V8H31FFQDVN4AMH6D/bin/fm-ensure-agents-md.sh .` in the worktree.
If this task produced durable project-intrinsic knowledge, record it in `AGENTS.md` as part of your change.
Keep it proportionate: skip `AGENTS.md` edits for trivial tasks that produced no durable project knowledge.

# Definition of done
This project ships **direct-PR**: you raise the PR yourself, without the no-mistakes pipeline.
The task is complete only when committed on your branch.
Before initial direct-PR publication:
1. Resolve exactly one validated `PUSH_ENDPOINT` from `git remote get-url --push --all origin`, including a configured `remote.origin.pushurl`; accept only one credential-free GitHub SSH or HTTPS endpoint with no query or fragment, and normalize it to `CURRENT_REMOTE_REPO=owner/repo` without `.git`. Define the portable helper `fm_checkpoint_mode() { if [ "$(uname)" = Darwin ]; then stat -f %Lp "$1" 2>/dev/null; else stat -c %a "$1" 2>/dev/null; fi; }` in this fresh-shell prelude. Query the live default with `git ls-remote --symref "$PUSH_ENDPOINT" HEAD`; require exactly one `ref: refs/heads/<branch> HEAD` symref and its matching HEAD OID, validate the short branch name, set `CURRENT_BASE_REF=refs/heads/<branch>`, `CURRENT_BASE_BRANCH=<branch>`, and `DEFAULT=$CURRENT_BASE_BRANCH`, and require `git ls-remote --exit-code "$PUSH_ENDPOINT" "$CURRENT_BASE_REF"` to return exactly that one base OID and set `LIVE_BASE_OID` to it. Lookup failure, missing or ambiguous symref or OID, multiple push endpoints, an unsupported URL, or ambiguity must append `blocked: direct-PR endpoint or base identity lookup failed; checkpoint retained` and stop. For a fresh workflow bind `PUSH_ENDPOINT` to that exact validated endpoint and set `REMOTE_REPO=$CURRENT_REMOTE_REPO`, `BASE_REF=$CURRENT_BASE_REF`, and `BASE_BRANCH=$CURRENT_BASE_BRANCH`. Before every recovery, fetch, lease lookup, push, or PR lookup, repeat these live endpoint and symref lookups and require the exact endpoint, canonical repository, full base ref, and short base branch to equal the bound `PUSH_ENDPOINT`, `REMOTE_REPO`, `BASE_REF`, and `BASE_BRANCH`; endpoint retargeting, default-branch change, lookup failure, deletion, or ambiguity must append `blocked: direct-PR endpoint or base identity changed; checkpoint retained` and stop without cleanup. Set `TASK_ID=parallel-direct-pr`, `FEATURE_REF=refs/heads/fm/parallel-direct-pr`, `BRANCH=fm/parallel-direct-pr`, `WORKFLOW=initial-publication`, `REPO_ID=$(git rev-parse --path-format=absolute --git-common-dir)`, `LEASE_CHECKPOINT='/tmp/no-mistakes-evidence/01KYG43W7V8H31FFQDVN4AMH6D/fm-home/state/parallel-direct-pr.direct-pr-lease'`, and `LEASE_CHECKPOINT_TMP=$LEASE_CHECKPOINT.tmp`. Derive `CURRENT_BRANCH=$(git symbolic-ref --quiet --short HEAD)` when attached. An attached branch must equal `BRANCH`; a detached `HEAD` is allowed only when `LEASE_CHECKPOINT` exists and step 3 proves an active rebase for the bound branch and base. Otherwise append `blocked: direct-PR requires attached branch fm/parallel-direct-pr` and stop. This Firstmate-owned task state is outside the project worktree and Git index.
2. Validate the primary checkpoint before handling the stable task-specific temporary target. Reject a checkpoint that is a symlink, non-regular file, unexpectedly owned, not mode 0600 according to `fm_checkpoint_mode "$LEASE_CHECKPOINT"`, or malformed by appending `blocked: direct-PR lease checkpoint invalid; refusing recovery` and stop. If `LEASE_CHECKPOINT_TMP` exists, reject it when it is a symlink, non-regular file, or unexpectedly owned; otherwise remove that exact same-task stale temporary file without parsing it, preserving the primary checkpoint. A valid checkpoint must contain exactly `repo=`, `push_endpoint=`, `remote_repo=`, `base_ref=`, `base_branch=`, `task=`, `feature_ref=`, `branch=`, `workflow=`, `expected=`, `default_oid=`, `pre_head=`, `post_head=`, `pr_url=`, `phase=`, and `attempt=`. Parse it only as data: read exactly one line for each literal key, split each line only at its first `=`, and assign `CHECKPOINT_RE

... [34789 bytes truncated] ...

lidated fields with mode 0600 through stable `LEASE_CHECKPOINT_TMP`, and rename it to `LEASE_CHECKPOINT`. On any such write or rename failure, remove only the validated stable `LEASE_CHECKPOINT_TMP`, retain the primary checkpoint, append `blocked: direct-PR publication checkpoint transition failed`, and stop without pushing. Classify `git ls-remote --exit-code "$PUSH_ENDPOINT" "$FEATURE_REF"` as exit 0 with its OID, exit 2 with no ref, or any other status as `blocked: remote feature retry lookup failed` and stop. Exit 0 at `POST_HEAD` proves publication already succeeded; exit 0 at `EXPECTED` means unchanged; a different OID or exit 2 means movement. On movement, remove the validated checkpoint and stable task-specific `LEASE_CHECKPOINT_TMP`, then restart safe validation. For `ATTEMPT=0`, atomically rewrite the validated checkpoint with `attempt=1` and, after the rename succeeds, set `ATTEMPT=1` in memory before the first `git push --force-with-lease="$FEATURE_REF:$EXPECTED" "$PUSH_ENDPOINT" "$POST_HEAD:$FEATURE_REF"`; a transition failure uses that cleanup and blocker without pushing. If that push fails or recovery starts with `ATTEMPT=1`, classify the remote first: published proceeds to the published-state transition, movement restarts, and unchanged atomically rewrites `attempt=2` and, after the rename succeeds, sets `ATTEMPT=2` in memory before the second and final identical push. If that transition fails, use that cleanup and blocker without pushing. If the second push fails or recovery starts with `ATTEMPT=2`, classify the remote first: published proceeds to the published-state transition, movement restarts, and unchanged atomically rewrites `phase=push-exhausted` with `attempt=2`, retains the checkpoint, appends `blocked: direct-PR publication retry exhausted; checkpoint retained`, and stops without another push. Recovery from `PHASE=push-exhausted` performs no remote lookup or automatic push and retains that blocker. After either push succeeds or remote classification proves `POST_HEAD` is published, atomically preserve `repo=$REPO_ID`, `push_endpoint=$PUSH_ENDPOINT`, `remote_repo=$REMOTE_REPO`, `base_ref=$BASE_REF`, `base_branch=$BASE_BRANCH`, `task=$TASK_ID`, `feature_ref=$FEATURE_REF`, `branch=$BRANCH`, `workflow=$WORKFLOW`, `expected=$EXPECTED`, `default_oid=$DEFAULT_OID`, `pre_head=$PRE_HEAD`, `post_head=$POST_HEAD`, and `attempt=$ATTEMPT`, set `pr_url=` empty and `phase=published-awaiting-pr`, write all sixteen fields with mode 0600 through stable `LEASE_CHECKPOINT_TMP`, and rename it to `LEASE_CHECKPOINT`. If that write or rename fails, remove only the validated stable `LEASE_CHECKPOINT_TMP`, retain the durable prior checkpoint, append `blocked: direct-PR published checkpoint transition failed; prior checkpoint retained`, and stop before any PR action. Then enter step 10.
10. PR reconciliation is a separate `PHASE=published-awaiting-pr` path. Require `WORKFLOW=post-conflict`, attached `CURRENT_BRANCH == BRANCH`, and current `HEAD == POST_HEAD`. Immediately repeat the step 1 exact endpoint and live symref revalidation, then revalidate `git ls-remote --exit-code "$PUSH_ENDPOINT" "$FEATURE_REF"`: exit 0 must return exactly `POST_HEAD`; exit 2 must append `blocked: published direct-PR feature ref deleted; checkpoint retained`; any other status must append `blocked: published direct-PR feature lookup failed; checkpoint retained`; and a different OID must append `blocked: published direct-PR feature moved; checkpoint retained`. Stop on every blocker without cleanup. List PRs in all states using the exact filters repository `REMOTE_REPO`, head `BRANCH`, and base branch `BASE_BRANCH`, and require every match to have those exact repository, head, and base identities. Require exactly one match. No match, multiple matches, or lookup failure must append `blocked: direct-PR PR identity reconciliation failed; checkpoint retained` and stop. If the sole match is closed or merged, append `blocked: direct-PR PR is {url} ({state}); checkpoint retained` and stop without creating a replacement. Require the sole open PR head OID to equal `POST_HEAD`; otherwise append `blocked: direct-PR PR head moved; checkpoint retained` and stop. When `PHASE=published-awaiting-pr`, after exact PR reconciliation append exactly one nonterminal status line `PR ready: {url} checkpoint=$LEASE_CHECKPOINT task=$TASK_ID workflow=$WORKFLOW post_head=$POST_HEAD` and stop, retaining both exact state files. This existing actionable status must wake Firstmate immediately through the shared status classifier. Do not run `fm-pr-check`, remove checkpoint state, or emit `done`. The handoff authorizes only the Firstmate-owned phase transition below; the worker remains stopped until an exact `pr-check-confirmed` receipt is present.
Firstmate-owned phase transitions: before invoking the guarded check, Firstmate must parse the primary sixteen-field checkpoint, revalidate the exact repository/head/base identity and require the PR head OID equals `POST_HEAD`, then atomically preserve every field, set canonical `pr_url={canonical url}` and `phase=pr-check-pending`, write mode 0600 through `LEASE_CHECKPOINT_TMP`, and rename it to `LEASE_CHECKPOINT`; failure retains the prior checkpoint and blocks before the check. In `pr-check-pending`, Firstmate must invoke the operational helper at the validated safe path `"$FM_ROOT/bin/fm-pr-check.sh" --expected-head "$POST_HEAD" --prior-head "$EXPECTED" --expected-repo "$REMOTE_REPO" --expected-base "$BASE_BRANCH" --expected-branch "$BRANCH" "$TASK_ID" "$PR_URL"`; it uses the canonical Firstmate state directory and `"$FM_ROOT/bin/fm-pr-poll.sh"` template and validates the complete task, repository, PR URL, base, feature branch, and head identity before any write. The helper accepts only one of three states: a complete internally consistent artifact set already bound to `POST_HEAD`, a complete internally consistent artifact set bound to the same task and canonical identity at the immediately checkpointed prior published head `EXPECTED`, or metadata with neither `pr=` nor `pr_head=` and all three poll artifacts provably absent. Exact `POST_HEAD` artifacts return success without republishing. Exact prior-generation artifacts are transactionally replaced for `POST_HEAD`; the helper refuses partial, foreign, ambiguous, unbound, or any other generation. Its expected-head guard must successfully resolve the live canonical PR head as exactly `POST_HEAD` before metadata, poll, registration, migration, or retirement writes, and lookup failure or mismatch must produce zero writes. After helper exit zero, Firstmate must use that same operational validation interface to require the complete exact artifact set and metadata `pr_head=POST_HEAD`, then atomically advance to `pr-check-confirmed` without a second publication. Any failed or interrupted publication retains `pr-check-pending`, appends `blocked: direct-PR PR-check artifact reconciliation failed; checkpoint retained`, and never reruns unless the guarded helper proves an exact prior generation or provable absence. Both phase transitions preserve all sixteen fields, the canonical `PR_URL`, exact `POST_HEAD`, mode 0600, stable-temp atomic rename, and prior-checkpoint retention on failure. In `pr-check-confirmed`, the worker must require that exact receipt, repeat all live endpoint, default, feature, and PR identity checks including head OID equality, validate the private namespace contains only `BASE_FETCH_REF` and `FEATURE_FETCH_REF`, delete both exact refs in one `git update-ref --stdin` transaction, remove only the validated checkpoint files, and emit the single terminal `done`; any failure retains state and blocks without publishing metadata or a poll again.
After initial PR opening, or after Firstmate-confirmed post-conflict cleanup, append `done: PR {url}` to the status file and stop. The post-conflict `PR ready:` handoff is nonterminal and must not use this line. When that later `done` arrives, Firstmate relays the already-armed PR state and must not run `fm-pr-check` or publish its metadata and poll a second time.
Do NOT run /no-mistakes. The captain reviews and merges the PR; firstmate relays it.
- Outcome: 🔧 1 issue found → auto-fixed (2) ✅ across 3 runs (2h10m11s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

🔧 **Review** - 2 issues found → auto-fixed (4) ✅
  • 🚨 bin/fm-pr-check.sh:258 - Replacement publishing overwrites data, registration, then check while the old generation remains. A crash after either of the first two writes creates mixed artifacts that both recovery validators reject, permanently wedging guarded PR recovery. Retire the validated old generation immediately after publishing the replacement receipt, before publishing new artifacts.
  • 🚨 bin/fm-pr-check.sh:80 - Guarded mode runs retirement recovery—which can delete poll artifacts and its receipt—before validating the live PR identity and expected head. A lookup failure or mismatch therefore changes durable state despite the new zero-write contract. Perform guarded live validation first, while retaining early recovery only for unguarded calls.

🔧 Fix: Captain, harden guarded PR replacement crash recovery
3 errors still open:

  • 🚨 bin/fm-pr-check.sh:208 - An unguarded invocation can run while a guarded replacement receipt is active. If the optional head lookup fails, metadata is rewritten without pr_head, making every later guarded recovery reject the receipt. Refuse unguarded publication while a replacement receipt exists, or recover it using receipt-bound authority first.
  • 🚨 bin/fm-pr-lib.sh:854 - Recovery validates one artifact generation, but removal recomputes hashes and identities afterward. A concurrent publication between those steps is therefore treated as deletion authority and removed despite not being the validated generation. Remove only using identities and hashes captured during validation, failing if any path changed.
  • 🚨 bin/fm-pr-check.sh:131 - Active-receipt recovery validates the expected head but not the supplied prior head. A retry with the correct expected head and a wrong prior head can delete the valid prior generation before the request is rejected. Bind recovery to both receipt heads before any mutation.

🔧 Fix: Captain, bind guarded recovery to validated receipt authority
2 errors still open:

  • 🚨 bin/fm-pr-check.sh:77 - The receipt refusal is only a point-in-time check. An unguarded process can pass it, wait while a guarded replacement completes, then overwrite the guarded metadata and artifacts without a head binding. Hold a per-task lock from receipt inspection through metadata and artifact publication.
  • 🚨 bin/fm-pr-lib.sh:943 - Captured identities do not make deletion atomic: remove_exact checks identity and hash before deleting by pathname. A concurrent publisher can replace the path between that check and rm, causing its new file to be deleted and leaving a partial generation. Serialize all same-task publishers and removers, or atomically quarantine the captured files before validation.

🔧 Fix: Captain, serialize PR poll publication and recovery
2 errors still open:

  • 🚨 bin/fm-pr-check-migrate.sh:1080 - Migration still quarantines and republishes task poll artifacts without the new per-task lock. A standalone migration can race a locked publisher or teardown and recreate or partially remove that task’s poll. Run migration before fm-pr-check takes its task lock, then acquire each affected task lock during migration using one consistent WATCH_LOCK → task-lock order.
  • 🚨 bin/fm-teardown.sh:364 - Teardown releases the task lock before deleting task metadata. A waiting fm-pr-check can acquire the lock, republish a complete poll while metadata still exists, and then have teardown remove the metadata, leaving orphan authenticated artifacts. Hold the lock through metadata and related task-state deletion, including the child teardown path.

🔧 Fix: Enforce migration and teardown task lock ordering
✅ Re-checked - no issues remain.

🔧 **Test** - 1 issue found → auto-fixed (2) ✅
  • 🚨 tests failed with exit code 1
  • bash bin/fm-run-behavior-tests.sh

🔧 Fix: Fix hermetic Herdr and teardown test fixtures
1 error still open:

  • 🚨 tests failed with exit code 1
  • bash bin/fm-run-behavior-tests.sh

🔧 Fix: Fix watcher migration lock ordering
✅ Re-checked - no issues remain.

  • bash bin/fm-run-behavior-tests.sh
  • Baseline supplied by the validator: bash bin/fm-run-behavior-tests.sh
  • bash tests/fm-backend-herdr.test.sh
  • bash tests/fm-backend.test.sh and bash tests/fm-gotmp.test.sh (direct gate-worktree runs hit the expected lifecycle refusal; both passed through the isolated behavior runner)
  • bash tests/fm-instruction-owners.test.sh
  • bash tests/fm-watcher-lock.test.sh
  • First aggregate bash bin/fm-run-behavior-tests.sh exposed the fake-Herdr log interleaving
  • Five consecutive post-fix runs of bash tests/fm-backend-herdr.test.sh
  • Final post-fix bash bin/fm-run-behavior-tests.sh
  • FM_HOME=/tmp/no-mistakes-evidence/01KYG43W7V8H31FFQDVN4AMH6D/fm-home bash bin/fm-brief.sh parallel-direct-pr direct-project
  • Rendered AGENTS.md section ### Intake into /tmp/no-mistakes-evidence/01KYG43W7V8H31FFQDVN4AMH6D/intake-contract.md
✅ **Document** - passed

✅ No issues found.

🔧 **Lint** - 1 issue found → auto-fixed ✅
  • ⚠️ linter found issues (exit code 1)

🔧 Fix: Captain: fix ShellCheck warnings
✅ Re-checked - no issues remain.

✅ **Push** - passed

✅ No issues found.

@JTInventory
JTInventory force-pushed the fm/firstmate-adopt-phase4-934-0726 branch from 14dcb11 to 362d2b1 Compare July 27, 2026 14:10
@JTInventory
JTInventory merged commit a667a02 into main Jul 27, 2026
6 checks passed
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant