Skip to content

feat(bin): merge upstream main while preserving fork delivery and supervision deltas - #52

Merged
jredgard merged 249 commits into
mainfrom
fm/fm-upstream-sync-3-20261023
Oct 23, 2026
Merged

jredgard merged 249 commits into
mainfrom
fm/fm-upstream-sync-3-20261023

Conversation

@jredgard

@jredgard jredgard commented Oct 23, 2026 •

Copy link
Copy Markdown
Owner
  • .agents/skills/afk/SKILL.md: Combined upstream spend-at-entry/return changes with fork uber refresh, guarded cleanup and escalation evidence.
  • .agents/skills/agent-skill-trigger-index/SKILL.md: Kept upstream trigger-index shortening and restored fork opentofu-iac-management and md-conflict-fastpath entries.
  • .agents/skills/ahoy/SKILL.md: Upstream version: fork blob is an older upstream version
  • .agents/skills/away-quiet-supervision/SKILL.md: Restored fork guarded authority, uber refresh/exit and Pi main-parking rules; upstream left this owner unchanged since b062eb9.
  • .agents/skills/bearings/SKILL.md: Upstream version: fork blob is an older upstream version
  • .agents/skills/bootstrap-diagnostics/SKILL.md: Upstream MISSING/OUTDATED distinction and triggers with fork no-mistakes 1.48.0 floor.
  • .agents/skills/harness-adapters/references/harness/opencode.md: Upstream version: fork blob is an older upstream version
  • .agents/skills/operational-home-layout/SKILL.md: Retained upstream owner changes and restored fork pr-poll-last-reported and conflict-radar state entries.
  • .agents/skills/process-event-sources/SKILL.md: Upstream version: fork blob is an older upstream version
  • .agents/skills/quiet/SKILL.md: Kept Pi/host quiet-with-words entry and no-daemon behavior while incorporating upstream current skill body.
  • .agents/skills/session-start-recovery/SKILL.md: Took exact current upstream skill/trigger owner; dropped fork duplicate edits in this moved/shortened skill family as directed, retaining fork feature skills and runtime owners.
  • .agents/skills/ship-landing/SKILL.md: Retained upstream additions and restored fork own-PR merged-PR teardown instruction plus conflict-radar and md-conflict-fastpath pointers.
  • .agents/skills/validation-supervision/SKILL.md: Restored fork conflict-radar and md-conflict-fastpath routing pointers; upstream left this owner unchanged since b062eb9.
  • .github/workflows/ci.yml: Retained upstream workflow/skip controls and fork serial host shard and Pi pins; snapshot count and diagnostic expect the restored 19-result suite.
  • .pi/extensions/fm-branch-supervision.ts: Kept quiet posture read through the shell owner, immediate quiet outcomes and endpoint bindings; adopted upstream async dispatch, current SDK processing boundaries, startup-context delivery and routing revalidation.
  • .pi/extensions/fm-primary-pi-watch.ts: Kept quiet-with-words routing and parked-main only for away; adopted upstream watcher generation, startup-context and custom-message boundary fixes.
  • .pi/extensions/lib/fm-branch-dispatch.ts: Kept quiet posture/authority and recorded backend-target routing; combined upstream fail-closed ambiguous alias detection and scoped decision routing.
  • AGENTS.md: Upstream supervisor contract; retained quiet Pi main-parking distinction, ADO waiver wording, per-run lint-skip intake and uber skill triggers; combined GitLab null-pipeline waiver.
  • README.md: Kept fork read-only conflict-radar entry; adopted upstream installation, runtime backend and skill layout updates.
  • bin/backends/herdr.sh: Upstream version: fork blob is an older upstream version
  • bin/fm-afk-contract.sh: Kept quiet authority-present owner and word-bearing quiet announcement; adopted upstream spend-at-entry record format and validation.
  • bin/fm-afk-launch.sh: Kept no-daemon quiet entry on ready host and Pi primaries, quiet rollback and words; adopted upstream entry spend and current host readiness checks.
  • bin/fm-afk-return.sh: Adopted upstream spend/return accounting; retained task-owned --merged-pr cleanup command in landed-worker catch-up.
  • bin/fm-backend.sh: Upstream version: fork blob is an older upstream version
  • bin/fm-backlog-transition-lib.sh: Adopted upstream note-label helper and GitLab closure; added ADO label to preserve atomic Done conversion and replay.
  • bin/fm-bootstrap.sh: Upstream MISSING/OUTDATED distinction with retained fork no-mistakes 1.48.0 floor.
  • bin/fm-branch-dispatch.mjs: Kept --quiet wake tail and shell-owned endpoint binding diagnostics; adopted upstream scope/offer interface and fail-closed routing.
  • bin/fm-branch-prompt.sh: Kept guarded quiet/away merge authority, own-PR cleanup and routine unsticking; retained upstream completed-scout policy delivery and GitLab null-pipeline refusal.
  • bin/fm-branch-report.sh: Kept away-host main-action classification, durable lane/decision handbacks and already-closed guard; adopted upstream current outcome store and return relay.
  • bin/fm-brief.sh: Combined fork lint-skip and run-versus-reattach contract with upstream forge/external, base and publication-consent contract.
  • bin/fm-captain-hold.sh: Used upstream bounded Done-archive replay and note-label helper; ADO closure remains through the combined shared helper.
  • bin/fm-config-inherit-lib.sh: Upstream version: fork blob is an older upstream version
  • bin/fm-contributions.sh: Upstream version: fork blob is an older upstream version
  • bin/fm-control.sh: Kept pre-stop LiteLLM gateway validation alongside upstream per-secondmate pins and Polytoken preflight.
  • bin/fm-crew-state.sh: Kept canonical ADO completed/readback and live-run supersession; adopted upstream backend, liveness and current task-state reconciliation.
  • bin/fm-dod-lib.sh: Kept fork intent budget, custody, compaction and logged drives within upstream DoD; the strict CI-ready matcher recognizes canonical ADO pullrequest URLs.
  • bin/fm-fleet-snapshot.sh: Took upstream: equivalent file-based contribution JSON transport replaces fork duplicate; upstream also propagates assembly errors.
  • bin/fm-git-strip-ai-trailers.sh: Upstream version: fork blob is an older upstream version
  • bin/fm-lab-home.sh: Upstream version: fork blob is an older upstream version
  • bin/fm-lease-lib.sh: Kept quiet-with-words guarded PR permission while quiet spawn remains attended and away spend stays bounded; adopted upstream current lease/liveness guards.
  • bin/fm-lint.sh: Took current upstream including its memory cap and fallback, restoring fork comma-decimal clock handling within the upstream function.
  • bin/fm-merge-authority-lib.sh: Kept explicit quiet authority records and shared authority-present read; adopted upstream exact canonical merge identity and record locks.
  • bin/fm-nm-run-lib.sh: Upstream version: fork blob is an older upstream version
  • bin/fm-pr-check.sh: Kept fork ADO delivery and upstream providers; Forgejo ownership now uses the head ref from the one existing shared PR read, with foreign-ref refusal and unreadable-ref warning preserved.
  • bin/fm-pr-lib.sh: Combined upstream provider/record APIs with fork ADO completion/readback and own-PR authority; the existing Forgejo response also supplies its optional source ref.
  • bin/fm-pr-merge.sh: Combined ADO approval/completion, REST policy gate, +10 vote, exact-head squash, completed readback, approval actions and quiet authority with upstream Forgejo and GitLab null-pipeline guards.
  • bin/fm-pr-poll.sh: Kept bounded ADO completion, conflict and abandonment reporting with dedup receipt; added upstream Forgejo polling.
  • bin/fm-project-mode.sh: Combined fork nmskip/ledgers queries with upstream Forgejo/external registry bindings; retained fail-closed invalid values.
  • bin/fm-promote.sh: Combined explicit fork lint-skip metadata and DoD arguments with upstream publication-consent, base and external-forge refusal.
  • bin/fm-remote-secondmate-relaunch.sh: Retained fork literal STATUS heredoc repair with upstream bounded convergence changes; upstream still retains this path.
  • bin/fm-review-diff.sh: Upstream version: fork blob is an older upstream version
  • bin/fm-session-start.sh: Upstream version: fork blob is an older upstream version
  • bin/fm-spawn.sh: Combined fork auth/gateway forwarding and lint-skip metadata with upstream harness/backend, consent, Orca environment sources and secondmate hook-preserving settings files. Expanded Claude settings once and merged gateway settings into the secondmate file.
  • bin/fm-supervise-daemon.sh: Combined fork exact-blocker/bounded readable+hex evidence with upstream distinct pending/unproven-composer deferral reasons.
  • bin/fm-supervision-engine-lib.sh: Upstream version: fork blob is an older upstream version
  • bin/fm-supervision-host.sh: Kept quiet words, away lane handback, evidence-led routine unsticking and closed-task merge snapshots alongside upstream watcher handoff/continuity fixes.
  • bin/fm-teardown.sh: Combined upstream lifecycle checks with fork task-owned merged-PR guard; completed states are provider-bound, including Forgejo closed plus merged=true.
  • bin/fm-test-run.sh: Retained fork host-only serial shard, 35-minute modeled host budget and report-compact coverage; combined upstream expanded inventory and timing hints.
  • bin/fm-timeout-lib.sh: Upstream version: fork blob is an older upstream version
  • bin/fm-tool-update-check.sh: Upstream version: fork blob is an older upstream version
  • bin/fm-wake-lib.sh: Upstream version: fork blob is an older upstream version
  • bin/fm-watch.sh: Kept ADO conflict/abandonment state notifications and transition receipt handling; adopted upstream wedge deferrals/caps, PR health signals, daemon ownership and continuity.
  • docs/agent-control.md: Retained gateway binding refusal before worker stop; adopted upstream per-harness control and relaunch policy.
  • docs/architecture.md: Retained ADO delivery, quiet authority, approval/waiver and completed-head guards; adopted upstream watcher, Forgejo, external review and GitLab pipeline architecture.
  • docs/captain-hold-lifecycle.md: Adopted upstream bounded replay/note-label ownership; retained Azure DevOps PR closure notes through the common atomic transition helper.
  • docs/configuration.md: Kept fork auth-forward/gateway configuration, per-run lint skip and ledger schema, ADO completion/approval options and quiet records; incorporated upstream schemas and opt-ins without enabling flags.
  • docs/documentation-audiences.json: Union of upstream new surfaces and fork uber/conflict-radar/OpenTofu/md-fastpath inventory; removed retired decision-hold pointer.
  • docs/fm-test-portable-shards.md: Kept fork measured 1649201ms supervision-host sample and dedicated shard; incorporated other upstream timing changes.
  • docs/jev-guards.md: Upstream version: fork blob is an older upstream version
  • docs/pi-supervision-branch.md: Kept quiet outcomes/authority, own-PR cleanup and live away-prompt guard; adopted upstream SDK compatibility, processing boundaries and routing guidance.
  • docs/scripts.md: Updated shared script inventory for ADO PR guards, compactor and conflict radar; adopted upstream new backend/review owners.
  • docs/supervision-host.md: Kept quiet record entry, away completion/decision handback, own-PR cleanup, unsticking and closed-task sweep behavior; incorporated upstream continuity, return and engine guidance.
  • docs/supervision-protocols/supervision-host.md: Kept away main-handback continuation under the existing record; adopted upstream protocol and watcher ownership changes.
  • docs/turnend-guard.md: Upstream version: fork blob is an older upstream version
  • docs/verification/runtime-backends.md: Kept fork quiet/Pi/gateway and away-prompt verification entry points; adopted upstream current backend/harness records. Refreshed Claude 2.1.309 and Pi 0.87.1/1.2.0 active verification records after real engine/SDK guards passed, and executable Claude gateway settings after the stable spawn/relaunch regressions passed.
  • docs/watcher-continuity.md: Upstream version: fork blob is an older upstream version
  • tests/fm-afk-contract.test.sh: Kept fork quiet-word authority/record cases and upstream live-worker-count entry-spend matrix.
  • tests/fm-afk-inject-e2e.test.sh: Kept fork guarded task-owned landed-worker cleanup in the prompt; adopted upstream current operational-input sentinel fixtures.
  • tests/fm-afk-launch.test.sh: Kept fork quiet-with-words host entry, no-daemon persistence and archive cases; adopted upstream current launch readiness and spend accounting.
  • tests/fm-backend-tmux-smoke.test.sh: Kept fork generated-prompt safety expectations; adopted upstream exact window/pane/session targeting and grouped-session container cases.
  • tests/fm-backlog-atomicity.test.sh: Kept fork ADO closure/backfill cases and current upstream atomic transition and replay cases.
  • tests/fm-bootstrap-network-parallel.test.sh: Upstream version: fork blob is an older upstream version
  • tests/fm-bootstrap.test.sh: Upstream installed/absent/outdated matrix adjusted to retained 1.48.0 floor.
  • tests/fm-branch-supervision.test.sh: Kept fork quiet/away permission and task-owned teardown prompt cases; adopted upstream completed-scout cleanup and pinned-holder re-anchor cases.
  • tests/fm-brief.test.sh: Kept fork explicit lint-skip and run-versus-reattach matrix; adopted upstream Forgejo/external delivery, safe inbox listing and consent cases.
  • tests/fm-captain-hold-lifecycle.test.sh: Kept fork ADO/Gerrit answer-before-cleanup replay; adopted upstream configured Done-archive answer lookup cases.
  • tests/fm-claude-trust.test.sh: Upstream version: fork blob is an older upstream version
  • tests/fm-composer-lib.test.sh: Upstream version: fork blob is an older upstream version
  • tests/fm-contributions.test.sh: Union of fork quiet-word authority case and upstream retry/review-thread cases.
  • tests/fm-control-relaunch.test.sh: Kept fork gateway reconstruction and refusal-before-stop cases; adopted upstream per-mate pins, immutable personal settings and away relaunch cases. Gateway parsing uses the shared launch-prefix helper.
  • tests/fm-daemon.test.sh: Union of fork exact-blocker evidence cases and upstream pending-versus-unrecognized deferral cases.
  • tests/fm-dispatch-resolve.test.sh: Upstream version: fork blob is an older upstream version
  • tests/fm-dod-lib.test.sh: Merged upstream and fork cases, adding behavioral ADO CI-ready done gating coverage.
  • tests/fm-git-strip-ai-trailers.test.sh: Upstream version: fork blob is an older upstream version
  • tests/fm-kimi-harness.test.sh: Upstream version: fork blob is an older upstream version
  • tests/fm-lint.test.sh: Retained upstream suite, restored comma-locale regression with epoch-timestamp assertions, and corrected the empty assignment literal for ShellCheck.
  • tests/fm-live-lab.test.sh: Upstream version: fork blob is an older upstream version
  • tests/fm-pending-reply.test.sh: Upstream version: fork blob is an older upstream version
  • tests/fm-pi-branch-extension.test.sh: Union of quiet/endpoint fork cases and upstream SDK/alias regressions; copied the shared shell endpoint owner into isolated fixtures, declared Orca backend for terminal targets, and gave quiet tasks distinct endpoints.
  • tests/fm-pi-branch-live-e2e.test.sh: Retained fork quiet-outcome assertions and upstream real-SDK startup-context and retry probes.
  • tests/fm-pi-watch-extension.test.sh: Kept fork quiet-word/empty-quiet routing; adopted upstream current SDK generation, process-group cleanup and OpenCode 2 plugin boundary cases.
  • tests/fm-pipeline-spend.test.sh: Upstream version: fork blob is an older upstream version
  • tests/fm-pr-check-security.test.sh: Retained upstream/fork provider checks and added Forgejo ownership tests for initial and merge-time registration; kept the merge-watch comment with its test.
  • tests/fm-pr-merge.test.sh: Kept fork bounded ADO REST/policy/vote/options/exact-head completion, quiet authority and GitHub reviewer cases; adopted upstream Forgejo complete/readback matrix and GitLab null-pipeline waiver cases.
  • tests/fm-procevent.test.sh: Kept fork fixture isolation while adopting upstream list-form nested targets, attachments, multiline prompts and malformed-item refusal cases.
  • tests/fm-project-capacity.test.sh: Upstream version: fork blob is an older upstream version
  • tests/fm-review-diff.test.sh: Upstream version: fork blob is an older upstream version
  • tests/fm-secondmate-harness.test.sh: Kept fork explicit lint-skip spawn metadata expectations; adopted upstream per-mate pin and malformed/legacy pin matrices.
  • tests/fm-spawn-dispatch-profile.test.sh: Kept fork auth forwarding, gateway secret isolation/rotation and lint-skip cases; adopted upstream resolved OpenCode 1/2 executable and delivery matrix, inherited Git config and hook-preserving secondmate settings. Gateway coverage now includes secondmates and preserved personal permissions.
  • tests/fm-spawn-orca-worktree.test.sh: Upstream version: fork blob is an older upstream version
  • tests/fm-spawn-pool-base-freshen.test.sh: Upstream version: fork blob is an older upstream version
  • tests/fm-supervision-host-attended-live-e2e.test.sh: Kept fork task-owned teardown command assertions; adopted upstream current attended engine and watcher-continuity guard.
  • tests/fm-supervision-host.test.sh: Combined fork quiet, lane handback, routine unsticking and closed-sweep cases with upstream continuity tests. The tagged Windows fixture models severed POSIX ancestry; CI repair 562ed22 widens its handled wait from 250 to 450 polls for loaded fork runners, preserving every assertion.
  • tests/fm-teardown.test.sh: Combined provider fixtures and fork own-PR guards, adding Forgejo branch-actor completed-state coverage and correcting its quoted done literal.
  • tests/fm-test-fixtures.test.sh: Union of fork gateway environment-isolation and upstream inherited Git-location isolation checks.
  • tests/fm-test-run.test.sh: Combined upstream runner regressions with fork dedicated-host assertions; isolated ordinary boundary fixture weights from growing upstream inventory.
  • tests/fm-timeout-lib.test.sh: Upstream version: fork blob is an older upstream version
  • tests/fm-watch-arm.test.sh: Upstream version: fork blob is an older upstream version
  • tests/fm-watcher-lock.test.sh: Upstream version: fork blob is an older upstream version

Fork deltas after this sync

Resolution method: BEHIND-ONLY means the fork blob exactly matched an upstream historical blob; those paths take current upstream. Fork-owned overlaps were reconciled from last-synced upstream b062eb94, preserving both deltas. The complete blob manifest records every path.

Upstream 5d718d5d775780c5449313dc4a6f55f7fb3b9b01 is merged into fork a610a4e1a6d1099192f5a1243fea6c2951370806 with two parents in commit b820ccfa1215a9487396dc45e464b183a59de074.
The merge-base after the branch merge is 5d718d5d775780c5449313dc4a6f55f7fb3b9b01 (previously 1a814e4943b69db555cc93ce0f42ef6db685c567); landing this PR with a merge commit preserves it.

Kept:

  • Azure DevOps PR delivery and task-owned landed cleanup: bin/fm-pr-{lib,check,merge,poll}.sh, bin/fm-teardown.sh, bin/fm-backlog-transition-lib.sh, bin/fm-captain-hold.sh, bin/fm-crew-state.sh, bin/fm-check-register.sh, bin/fm-watch.sh, PR/teardown fixtures and tests; docs/{configuration,architecture,scripts,captain-hold-lifecycle,gitlab-merge-watch}.md; AGENTS.md.

  • Standalone 4000-character ADO PR compaction, verified step board and closed findings comment: bin/fm-pr-report-compact (single self-contained file, unchanged), tests/fm-pr-report-compact.test.sh, tests/fixtures/fm-pr-report-compact/az.

  • Captain-only intent, 900-character budget, CI-ready compaction, custody and foreground logged no-mistakes drives: bin/fm-dod-lib.sh, bin/fm-brief.sh, tests/fm-{dod-lib,brief}.test.sh, docs/{configuration,gerrit-forge-integration}.md.

  • Quiet-with-words and away guarded live-green PR merge authority on Claude and Pi: bin/fm-{afk-contract,afk-launch,merge-authority-lib,merge-outcome-lib,lease-lib,branch-prompt,branch-report,supervision-host}.sh, bin/fm-branch-dispatch.mjs, bin/fm-contributions.jq, .pi/extensions/{fm-branch-supervision,fm-primary-pi-watch,lib/fm-branch-dispatch}.ts; .agents/skills/{afk,quiet,uberafk,uberquiet}/SKILL.md; quiet/afk/Pi/branch tests and supervision docs.

  • Supervision-host lane completion/worker decision handback, task-owned teardown, routine unsticking and closed-task sweep suppression: bin/fm-{supervision-host,branch-report,branch-prompt,wake-drain,pr-lib,teardown,afk-return}.sh, tests/fm-supervision-host.test.sh, tests/fm-teardown-endpoint-safety.test.sh; docs/{supervision-host,pi-supervision-branch,supervision-protocols/supervision-host}.md.

  • Recorded backend targets for stale supervision routing: bin/fm-endpoint-records.sh, bin/fm-branch-dispatch.mjs, .pi/extensions/lib/fm-branch-dispatch.ts and branch/Pi/host tests.

  • Worker auth forwarding and LiteLLM gateway binding, including pre-stop relaunch refusal: bin/fm-{spawn,control,claude-gateway-lib}.sh (gateway settings retain worker attribution and secondmate hooks/personal permissions), docs/{configuration,agent-control}.md, .agents/skills/harness-adapters/references/harness/gemini.md, spawn/control/Muse fixture tests.

  • Fleet-sync origin upstream selection for main/master clones: bin/fm-fleet-sync.sh, tests/fm-fleet-sync.test.sh.

  • Per-run lint-skip intake and promotion/relaunch contract: bin/fm-{project-mode,brief,spawn,promote,dod-lib}.sh, AGENTS.md, .agents/skills/project-management/SKILL.md, related brief/spawn/DoD/report tests.

  • Documentation/mechanical conflict fast path, conflict radar and OpenTofu release guidance: .agents/skills/{md-conflict-fastpath,conflict-radar,opentofu-iac-management}/, bin/fm-conflict-radar.sh, tests/{md-conflict-fastpath,fm-conflict-radar}.test.sh, docs/verification/conflict-radar.md, documentation-audiences.json.

  • Fork CI shard layout and coherent Pi package tree: .github/workflows/ci.yml, bin/fm-test-run.sh, tests/fm-{test-run,ci-workflow}.test.sh, docs/fm-test-portable-shards.md.

  • Fork bootstrap floor, bounded escalation diagnostics and literal remote status marker: bin/fm-bootstrap.sh (1.48.0), CONTRIBUTING.md, .agents/skills/bootstrap-diagnostics/SKILL.md, bin/fm-supervise-daemon.sh, bin/fm-remote-secondmate-relaunch.sh, tests/fm-{bootstrap,daemon}.test.sh; docs/{verification/supervision,wedge-alarm,herdr-backend}.md.

  • Locale-safe lint clock: bin/fm-lint.sh, tests/fm-lint.test.sh; dot/comma decimal separators preserve epoch timestamps, durations and fallback deadlines, within upstream structure and memory cap.

  • Fork instruction owners, triggers and runtime paths: .agents/skills/{away-quiet-supervision,validation-supervision,ship-landing,agent-skill-trigger-index,operational-home-layout}/SKILL.md; guarded recorded authority, uber refresh/exit, own-PR teardown, conflict routing, OpenTofu/markdown triggers, and persisted state entries restored. The 55-path untouched-upstream audit identified the two dropped unchanged skill owners; other differences were decided lint changes, retained runner fixture adjustments, and restored snapshot regression coverage.

  • Large-input snapshot behavior coverage: tests/fm-fleet-snapshot-view.test.sh keeps the bounded-argv regression with fixture-derived record assertions; .github/workflows/ci.yml expects 19 results. The upstream implementation replaces the fork transport duplicate.

  • Integration fixes preserving fork gates across upstream providers: bin/fm-dod-lib.sh, tests/fm-dod-lib.test.sh, tests/fm-crew-state.test.sh, tests/fm-pr-check-security.test.sh recognize canonical ADO CI-ready notes; bin/fm-pr-lib.sh, bin/fm-pr-check.sh provide Forgejo ownership from the existing single PR read; bin/fm-teardown.sh, tests/fm-teardown.test.sh accept Forgejo closed+merged only for Forgejo. Matching/foreign/unreadable refs and pending/other-PR notifications are covered.

  • Retained shared delivery template and enforcement (workflow and verifier test are already byte-identical to upstream): .github/workflows/no-mistakes-required.yml, tests/fm-no-mistakes-required.test.sh, bin/fm-pr-lib.sh, bin/fm-pr-merge.sh; the pinned require-no-mistakes action checks the signature and head-bound pipeline attestation on opened, edited, synchronized and reopened submissions to main. The shared action validates the attestation; merge guards separately require head-bound CI/policy evidence. Intent, What Changed, Risk Assessment and verification material remain visible in this description.

Dropped or replaced:

  • Fork ADO-specific captain-hold closure formatting (bin/fm-captain-hold.sh): replaced by the upstream shared note-label helper and bounded replay; the combined helper retains ADO support in bin/fm-backlog-transition-lib.sh.
  • Fork contribution-snapshot transport implementation duplicate (bin/fm-fleet-snapshot.sh): upstream 1a868710, 072a7da3 and 25bb43bb preserve file transport and failure propagation. The fork bounded-argv behavior test is retained with fixture-derived record assertions; upstream failure-propagation tests remain alongside it.
  • Upstream owner moves/shortening and trigger-listing budget changes (2ce57d00, 9d76aa8e) remain; current session-start-recovery and scout-completion are adopted. The five fork feature-owner hunks initially dropped by mistake are restored and listed under Kept; no moved/retired skill is resurrected.
  • Retired decision-hold-lifecycle pointer: upstream captain-hold-lifecycle remains the owner; no resurrected pointer.
  • Fork lint memory-cap increase (bin/fm-lint.sh, tests/fm-lint.test.sh): upstream cap and bounded source-analysis fallback adopted as directed. The comma-locale clock handling and regression are retained; the fallback now receives correct epoch timestamps in comma-decimal locales.
  • All BEHIND-ONLY blobs and upstream-removed stale helper/test/doc files: current upstream versions or successors adopted, based on exact historical blob matching.

Upstream supervision fixes 05a68cb2 (kunchenguid#6588), ac8a767f (kunchenguid#7466), 11990a20 (kunchenguid#7444), f05b6284 (kunchenguid#5939), 5db719de (kunchenguid#7361), bd866530 (kunchenguid#7552), 0fc96515 (kunchenguid#5947), 8eb9be51 (kunchenguid#7437), 8818a804 (kunchenguid#6163), and 9cd4a3be (kunchenguid#7308) are retained.
They strengthen watcher continuity, wait evidence, caps, composer diagnostics and entry spend; they do not replace the fork's lane handback, own-PR guard, foreground drives, quiet authority or unsticking/sweep behavior.
Upstream PR changes 56074d03 (kunchenguid#7450), db03cb45 (kunchenguid#7627), 45dd50a5 (kunchenguid#7462), 6eb8b7f8 (kunchenguid#7630), c79cd403 (kunchenguid#7634), and 6df15481 (kunchenguid#7364) are retained alongside ADO delivery; none supplies ADO completion or the standalone report compactor.

What Changed

Merge upstream 5d718d5d into the fork through the two-parent merge b820ccfa, retaining fork behavior and upstream improvements. Validation commits correct locale timing, ADO CI-ready recognition, Forgejo ownership and teardown, retained skill owners, large-input snapshot coverage, and their coupled tests/CI checks. CI repair 562ed22 also gives the Windows tagged-lock fixture a bounded 450-poll handled wait; its focused tests and the full 97-case host suite pass locally. The path-by-path resolutions and kept/replaced inventory above record the final integration choices.

🤖 Generated with Claude Code

Risk Assessment

The update spans supervision, watcher continuity, worker launches and multiple forges. Named-head and policy checks, guarded recorded authority, and task-owned teardown remain required. Behavioral and live-harness evidence is described below. Local Ruby/GNU-time limitations and the approved inconclusive pipeline Test verdict are disclosed; required GitHub CI must pass before landing.

Validation

Pinned comparison heads: ours a610a4e1a6d1099192f5a1243fea6c2951370806; theirs 5d718d5d775780c5449313dc4a6f55f7fb3b9b01; merged is commit b820ccfa1215a9487396dc45e464b183a59de074, the two-parent merge of those exact commits.
At the submitted merge head b820ccf, tree b90bee95c1d5d10e3f80089079d5b508b0ffdee6 preserves all 24 fork-added files byte-for-byte; all 186 BEHIND-ONLY paths match upstream exactly and 139 fork differences remain. The validation fixes described above are descendants of that two-parent merge. The following three-head table records the pre-pipeline runs; current-head pipeline evidence and its approved limitations are reported separately below.
Baseline copies are Git archives with private Git metadata bound to their exact original commits, not managed worktrees. Their tracked content remains unchanged.

Commands: bash bin/fm-lint.sh; source-aware ShellCheck on all 94 changed bin scripts, plus final touched scripts/tests; bash bin/fm-doc-audience-check.sh; bash bin/fm-test-run.sh --check-coverage; and bash bin/fm-test-run.sh --json <task-proof>.json <suite paths> on each head.

Suite Ours Theirs Merged
tests/fm-pr-merge.test.sh PASS PASS PASS
tests/fm-pr-check-security.test.sh PASS PASS PASS
tests/fm-teardown.test.sh PASS PASS PASS
tests/fm-teardown-endpoint-safety.test.sh PASS PASS PASS
tests/fm-dod-lib.test.sh PASS PASS PASS
tests/fm-brief.test.sh PASS PASS PASS
tests/fm-afk-contract.test.sh PASS PASS PASS
tests/fm-afk-launch.test.sh PASS PASS PASS
tests/fm-afk-return.test.sh PASS PASS PASS
tests/fm-afk-inject-e2e.test.sh PASS PASS PASS
tests/fm-supervision-host.test.sh PASS FAIL 1 PASS
tests/fm-branch-supervision.test.sh PASS PASS PASS
tests/fm-spawn-dispatch-profile.test.sh PASS PASS PASS
tests/fm-spawn-compact-adviser-disable.test.sh PASS PASS PASS
tests/fm-spawn-pool-base-freshen.test.sh PASS PASS PASS
tests/fm-pr-report-compact.test.sh PASS Absent at this head PASS
tests/fm-fleet-sync.test.sh PASS PASS PASS
tests/fm-pi-branch-extension.test.sh PASS PASS PASS
tests/fm-pi-watch-extension.test.sh FAIL 1 PASS PASS
tests/fm-contributions.test.sh PASS PASS PASS
tests/fm-daemon.test.sh PASS PASS PASS
tests/fm-control-relaunch.test.sh PASS PASS PASS
tests/fm-backlog-atomicity.test.sh PASS PASS PASS
tests/fm-captain-hold-lifecycle.test.sh PASS PASS PASS
tests/fm-test-fixtures.test.sh PASS PASS PASS

Additional merged checks: strict TypeScript 5.9.3 production-consumer typechecks against Pi 1.1.0/1.2.0; bootstrap and self-repo consent; PR attestation enforcement; conflict radar and md-conflict fastpath; snapshot view and contribution input; documentation audience inventory; CI shard coverage.
Lint, source-aware ShellCheck, actionlint 1.7.12 and documentation audience checks pass; coverage reports 278 suites with nine serial shards and the dedicated host shard.

Real harness guards: Claude Code 2.1.309 passes guarded away merge interpretation (green covered PR merges synchronously; red, unreported and other-lane hold) and supervision-host continuity/resume. Pi SDK 0.87.1 and 1.2.0 pass quiet outcomes, startup-context and retry behavior with intercepted provider streams; 1.2.0 additionally passes prompt-bearing user/no-prompt custom events and cancellation boundaries. Pi 0.87.1 explicitly cannot exercise those newer custom-opening subprobes.

Host limitation: Ruby is absent. fm-ci-workflow.test.sh cannot execute its Ruby YAML parse; fm-test-run.test.sh passes the corrected packing-boundary cases then cannot execute its Ruby workflow assertions. The actual workflows pass pinned actionlint. These two suites remain incomplete here and must run in CI.

Superseded merge validation failures and corrections:

  • Teardown GitLab closure matrix now provides the merged GitHub REST readback required by the combined landed-work guard; corrected suite passes.
  • Pi isolated fixtures include the shared endpoint reader and backend declaration; quiet task endpoints are distinct. Conflicting aliases still refuse and now retain every-record diagnostics; final branch and strict typechecks pass.
  • Claude launch construction expands settings once; secondmate gateway fields share the upstream hook-preserving file. Gateway regression checks dynamic keys, attribution, hooks and personal permissions. Stable full spawn and relaunch runs pass.
  • Live host continuity fixture now emits a routine event, because the fork intentionally hands terminal lane completions back to main. Real Claude guard passes.
  • Windows tagged-lock fixture models the severed POSIX ancestry at pid 1. The original fixture could reach the real launching Codex on Linux, which correctly outranked its synthetic Windows bridge. Tagged and superseded-host cases pass in isolation; full host final result is shown above.
  • The bounded-argv snapshot behavior test is retained with updated fixture-derived assertions, and the coupled workflow pin and error text now expect 19 results.
  • Runner packing fixture isolates synthetic boundary weights from the expanded upstream suite inventory. Packing cases pass; Ruby limitation remains as stated.
  • Baseline archive initially lacked Git metadata, so its fixture suite could not clone itself. Both baseline copies were initialized at their pinned commits; the unchanged original-fork fixture suite passes on rerun.
  • Intermediate spawn/relaunch runs overlapped source edits and produced transient 127/unbound-array failures; both cases pass in isolation and stable final suites pass.

Reproduced upstream baseline failure: the untouched upstream supervision-host suite times out at the at-turn downtime-publication failure/Stop-hook case, and that exact case fails in isolation (upstream-host-timeout-focused.log). The merged complete host suite passes. No baseline source was modified.

Pre-existing baseline failure: the original fork Pi watcher suite rejects an external healthy OpenCode arm as owned. The merged watcher suite passes with upstream fixes. Original baseline failures remain in comparison-ours.log; corrective reruns never rewrite the original logs.

Evidence: targeted.log/.json, comparison-{ours,theirs}.log/.json, comparison-ours-fixture-retest.log/.json, final-host.log/.json, final-spawn.log/.json, control-final.log/.json, final-pi.log/.json, snapshot-equivalent.log/.json, live-host-retest.log/.json, live-away-prompt.log, pi-live-1.2-retest.log/.json, and lint/ShellCheck logs in this task proof directory.

Intent

Merge upstream/main into jredgard/firstmate with a real merge commit so merge-base advances. Preserve fork deltas: ADO REST completion, policies, vote, squash/head binding, completed readback, polling, landed-work/draft guards; standalone fm-pr-report-compact with tests/fixtures, 4000-char body and closed-findings comment; no-mistakes 900-char intent, done compaction and custody; Claude/Pi quiet words and away guarded merge authority; uberafk/uberquiet; host lane/decision handback, own-PR teardown, foreground logged validation, worker unsticking and closed-task sweep skips; launch auth forwarding; fleet-sync origin upstream fix; md-conflict-fastpath; fork CI shards. Pass lint and affected suites. PR must list each conflict resolution, every kept fork change and each duplicate dropped for upstream equivalence. Land with a merge commit, never squash.

Pipeline verification

Published head: 562ed22c10ff20955ba18f80732e7dc73d9272e6, a descendant of the real upstream merge. Document has no unresolved findings; canonical lint passes. The no-mistakes drive returned checks-passed for this published head; required GitHub CI has been read back green.

Firstmate approved the local Test exception after eight directly driven scenarios passed. The timed batch stopped before results for pr-check-security, pr-report-compact, afk-contract, afk-launch, fleet-sync and md-conflict-fastpath; their pipeline-batch outcomes are unknown. Pre-pipeline merged-head evidence is recorded above, and required CI must complete these checks before landing. GNU /usr/bin/time is absent here, so the inherited lint RSS case is unavailable; all other lint cases pass. Ruby-dependent workflow assertions are also unavailable locally. These are limitations, not local clean passes. The stored exception reads: "live validation verdict: inconclusive; Test exception approved: no operator reason supplied"; firstmate supplied the approval rationale in steering message 007, based on independent evidence and mandatory CI.

The old skill-restoration review finding was retained after its fixes; firstmate verified the five restorations at 5ef5d0c and explicitly approved that stale review evidence. CI repair 562ed22 widens the tagged-lock handled wait from 250 to 450 polls after the fork runner timed out; ShellCheck, the focused tagged/away cases and the full host suite (97 passes) pass locally. The first CI attempt recorded all 20 serial-1 scripts passing but its job was cancelled at the 30-minute limit; its fm-watch-triage runtime was 1603470 ms, and the same cancellation was reproduced on upstream runs. The fresh CI run passed all nineteen checks, including both serial lanes; its current-head results supersede the initial cancellation and tagged-lock fixture failure. Earlier corrections are preserved: locale-safe clock and its test; provider-bound Forgejo cleanup; ADO CI-ready recognition; Forgejo ownership from one existing read; restored skill owners and bounded-argv test; coupled CI count/comment alignment; ShellCheck-clean test literals.

Published pipeline evidence (full logs and scenarios remain in the linked evidence branch):

Pipeline

Updates from git push no-mistakes

  • ✅ intent - passed
  • ✅ rebase - passed
  • ⚠️ review - 1 warning
  • ⚠️ test - 3 issues (2 warnings, 1 info)
  • ✅ document - passed
  • ✅ lint - passed
  • ✅ push - passed
  • ⚠️ pr - running
  • ⚠️ ci - pending

Upstream opt-ins worth enabling

  • config/wedge-defer-worker-wait, config/wedge-defer-parked-gate, config/wedge-defer-validating, config/wedge-evidence-guards, and FM_WEDGE_MAX_ESCALATIONS: bound repeat alarms while quiet/away workers wait or validate.
  • config/ready-after-green: withhold child PR-ready lines until checks are green.
  • config/pr-health-monitor: notify owned PR conflicts and failing required checks; optional stale review-thread re-raises also warrant evaluation.
  • FM_CI_SKIP_PUSH and FM_CI_SKIP_MACOS repository variables: optional fork CI cost controls.

These are follow-up candidates only; this sync enables none of them.

Land with a merge commit, never squash

tiago-peixoto and others added 30 commits September 25, 2026 03:15
…m as silence (kunchenguid#5588)

* fix(bin): surface an unrecognized status prefix instead of dropping it

A parked or holding declaration, and a verb whose correlation token did not parse, never became an event, so the supervisor still saw the earlier line.

* no-mistakes(review): Require verb-shaped unrecognized status prefixes, add continuation tests

* no-mistakes(document): Document unrecognized status prefix escalation in afk skill

* no-mistakes(ci): I reproduced the "Behavior portable serial 6" failure locally and fixed it by changing the test data in one test. No product code changed. **What failed:** `tests/fm-session-start.test.sh`, in `test_orphan_status_logs_are_printed`, with "matched status log was printed 2 times". **Why:** the test writes status lines with made-up prefixes, `matched: surfaced once` and `orphan: step N`. The test only uses them as placeholder text. It checks that the session-start digest prints each task's status tail exactly once. This PR (kunchenguid#4763) deliberately makes an unrecognized one-word lowercase prefix a status event. So those lines now surface as captain-relevant events, and the wake queue's STATUS OUTCOME BACKSTOP section prints them a second time. The code under review is behaving as the issue asks. Only the test's placeholder data had become meaningful. **Rule the test depends on:** its status lines must not be captain-relevant, so the digest is the only place they are printed. Both lines in this test broke that rule. The orphan line would have failed the same count check right after the matched line did. **Fix:** in that test only, I switched both lines to the recognized, non-captain verb `working:`: `working: surfaced once` and `working: orphan step 1..6`. I updated the matching assertions and counts to use the new text. What the test checks is unchanged: orphan logs are labelled, the tail is bounded, the log path is printed, and each tail appears once. **Verification:** before the fix, the test failed locally the same way as in CI. After it, `bash tests/fm-session-start.test.sh` reports "all assertions passed

* no-mistakes(review): Detect unrecognized prefixes on unstamped lines; share verb list

---------

Co-authored-by: Kun's firstmate <kunchenguid+firstmate@users.noreply.github.com>
…henguid#5658)

Fixes kunchenguid#5295

Session start now reports a remote inheritance failure using the
push's own error line instead of the first unchanged item that
happened to print before it, and the shared captain preferences
header check now names the first required phrase it did not find,
on both the local and remote inheritance paths.
…yloads (kunchenguid#5657)

* fix(bin): stand down the Claude Stop auto-arm on pi-code-delivered payloads

pi-code loads the tracked Claude settings but has no asyncRewake, so it
awaits every Stop hook; without a stand-down the auto-arm runs
synchronously inside Pi's turn end and holds it open for the declared
multi-hour timeout. Stand down when the payload's transcript_path
contains a /.pi/ path component, the same discriminator the closed-but-
unmerged fix in kunchenguid#3352 used, with an explicit string-type check on the
jq filter.

Fixes kunchenguid#3343

* no-mistakes(document): document pi-code stand-down in harness integrations reference
…kunchenguid#5659)

* fix(bin): match whole multi-word project names in the registry lookup

bin/fm-project-mode.sh matched a registered project name against only the
first whitespace-delimited token of a registry row, so a name containing a
space never matched, silently defaulting the project to no-mistakes off
instead of its declared posture.

The lookup now matches the whole registered name against the raw line text,
so a name is compared literally (never as a regex) and a name that is a
leading prefix of another registered name still resolves to its own row.

* no-mistakes(document): docs already accurate for multiword registry name match

* chore: drop accidental empty err file

Co-authored-by: Kun Chen <kunchenguid@users.noreply.github.com>

---------

Co-authored-by: Cursor Agent <cursoragent@cursor.com>
Co-authored-by: Kun Chen <kunchenguid@users.noreply.github.com>
…d#5546)

* fix(bin): classify the stdin program of `bash -s` with operands in the arm policy

With -s, sh/bash/zsh read the program from stdin even when operands follow;
the operands are only positional parameters. The arm policy treated the first
operand as a script path, so heredoc and here-string payloads were never
classified and a hidden bin/fm-watch.sh execution was allowed.

A protected path in the operand position still fails closed as before.

Fixes kunchenguid#1489

* no-mistakes(document): Clarify stdin shell operand documentation

* no-mistakes(ci): Captain, fixed `shellInvocation` so `bash -- -s` treats `-s` as a script name, and updated R21 to test the exact command. The targeted policy suite, lint, documentation check, and diff check pass. Both hosted workflows show `action_required` before any jobs ran; that external approval state remains unresolved

* fix(bin): keep main's handling of words after a leading `--`

Revert the pipeline CI-step change that made the first word after a leading
`--` always a script. It turned forms that main denies today into allow
(for example `bash -- -c 'bin/fm-watch.sh'`), which is outside kunchenguid#1489 and
loosens a fail-closed policy. `--` after `-s` still ends option parsing.
…nchenguid#5695)

* fix(bin): strip AI co-author trailers from fleet-launched commits

Cursor and other non-Claude runtimes append the trailer after the typed
message. A per-task commit-msg hook removes it and leaves human co-authors
and the author identity untouched.

* no-mistakes(review): Export pane hooksPath override and drop generated-with stripping

* no-mistakes(ci): This PR caused all three CI failures, and the fix is test-only: 4 test files change, no product code. **Cause.** `fm-spawn.sh` now installs the AI-trailer strip hooks for every spawn, secondmates included. The installer refuses a worktree that is not a git repository, and the PR deliberately keeps that fail-closed rule because real secondmate homes are firstmate clones. Four test fixtures still gave secondmates a plain directory as their home, so each spawn failed with "not a git worktree ... could not install the AI-trailer strip hooks": - serial 5: `tests/fm-backlog-atomicity.test.sh` ("secondmate spawn failed"). - serial 8: `tests/fm-secondmate-harness.test.sh` ("split: no meta written"). - Herdr: `tests/fm-backend-herdr-launcher-workspace-e2e.test.sh` and `tests/fm-backend-herdr-workspace-per-home-e2e.test.sh`. **Rule that must hold.** Every home a test spawns as a secondmate must be a git worktree. I checked the other places in the changed area: the only secondmate spawns in these tests are the ones listed. The earlier rounds already fixed the other fixtures (`fm-secondmate-liveness`, `fm-secondmate-safety`) the same way. **Fix.** - Each of those four secondmate homes now gets the same `.gitignore` plus `git init -q -b main` that the liveness and safety tests already use. - The two Herdr tests clean up with their own plain `rm -rf "$TMP_ROOT"`, not the shared `tests/lib.sh` helper. Because the installer leaves each `state/<id>.git-hooks` directory read-only, that cleanup printed "Permission denied" and left the directories behind. Both cleanups now restore the owner's write bit on every directory before removing (`find ... -exec chmod u+rwx`), which is what `fm_test_remove_tree` in `tests/lib.sh` does. **Verification.** - `tests/fm-secondmate-harness.test.sh` passes. - `tests/fm-backlog-atomicity.test.sh` passes (99 ok, exit 0). - shellcheck is clean on all four files. - I could not run the two real-Herdr tests locally: the Herdr lab on this host refuses to start because it needs exactly one running default session, and I did not change the host's Herdr state to get around that. Instead I checked their two changed steps directly: the installer succeeds on a home set up the new way, and the new cleanup removes the read-only hooks directory completely. Those two tests will only be proven on CI
…id#5683)

* fix(bin): treat Pi's dollar-first cost footer as furniture

An idle Pi status row opening with $0.000 was read as a dead-shell prompt, so exit and relaunch refused on an empty composer.

* test: wait for the draining holder to exec sleep before reading its identity

The procevent drain fixture read fm_pid_identity immediately after
backgrounding setsid sleep, racing the child's exec chain. Mid-exec the
cmdline can read empty, failing the fixture on a loaded CI runner.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…5534)

* fix(bin): refuse a merge when a required check never reported

fm-pr-merge.sh built its GitHub refusals only from checks present in
statusCheckRollup, so a required check that never ran was simply absent and
the merge proceeded on the subset that reported, contradicting its own
"every required check green" claim.

The GitHub verify now reads the base branch's required contexts from the
forge itself - the classic branch protection summary on
GET repos/{o}/{r}/branches/{b} and the active ruleset rules on
GET repos/{o}/{r}/rules/branches/{b} - and refuses when a required context
has no entry in the same rollup, at the same head, that the merge is bound
to. Absence reads as unknown, never green. The required-set read joins the
existing refusal list, so a draft, a red check, and an unreported required
check are all reported together.

Could not read vs nothing required: both endpoints need only repository
read access. The admin-only GET .../branches/{b}/protection endpoint is
deliberately not used: it answers a non-admin token with the same 404 an
unprotected branch gets (observed live on kunchenguid/firstmate main with
this token), which would read a missing permission as "nothing required".
Any failed or malformed read of either source (auth, missing fine-grained
permission, rate limit, network, 404, unexpected shape) refuses the merge
with a line naming the unreadable source. The one exception is GitHub's
plan-gated 403 on the rules endpoint ("Upgrade to GitHub Pro or make this
repository public"), which already means "this repository has no branch
rules" for the merge-queue reader; that check moves into one shared helper
and the classic summary still decides for such a repository.

Attended waiver: --allow-missing <check-name> is the twin of --allow-red and
follows the same design and recording path: once, separate name argument,
waives only that exact unreported required check, still requires every
other required check reported and every check green, never waives an
unreadable required set, refused while the away-posture record exists, and
refused on GitLab. Merge-state BLOCKED policy is unchanged.

How this differs from the withdrawn kunchenguid#5353 (read from its diff):
- kunchenguid#5353 read the admin-only branches/{b}/protection endpoint and treated
  its 404 as "no required checks", so for any non-admin token the required
  set silently read as empty; this change reads the read-access branch
  summary and treats every failure as unreadable.
- kunchenguid#5353 ignored rulesets; this change also reads required_status_checks
  rules from the effective branch rules.
- kunchenguid#5353 made separate per-head REST reads of statuses and check-runs capped
  at per_page=100 with no pagination; this change checks presence in the
  same statusCheckRollup view the red-check gate already reads at the
  verified head.
- kunchenguid#5353 stopped at the first unreadable read; this change reports it as one
  refusal among all the others.
- kunchenguid#5353 also claimed kunchenguid#5345 (lock stealing) and changed 39 files, most
  unrelated; this change is kunchenguid#5344 only.

Live proof, read-only (a gh wrapper refused every merge and mutating call):
- cli/cli#14474 (trunk requires 3 classic build contexts, none ran):
  refused, naming build (macos-latest), build (ubuntu-latest),
  build (windows-latest); with --allow-missing "build (macos-latest)" it
  still refused, naming the other two.
- cli/cli#13665 (ran build (ubuntu-24.04-firewall) instead): refused,
  naming build (ubuntu-latest).
- hashicorp/terraform#39262 (ruleset-required checks absent): refused,
  naming Code Consistency Checks, End-to-end Tests, Race Tests, Unit Tests.
- cli/cli#14485 (all required reported and green): verified; the wrapper
  blocked the merge call and the pull request read back open.

Fixes kunchenguid#5344

* fix(review): Preserve required-check producers and aggregate independent read failures

* fix(document): Clarify required-check verification and waiver documentation

* fix(bin): match an app-bound required commit status by name

The producer-identity check resolved an app-bound required context only
against check runs, so a required context that the required app reports as
a commit status could never match and always read as "has not reported".
A commit status carries no app id to compare, so an app-bound requirement
that arrives as a status now matches by name, as before producer binding;
check runs keep requiring the configured producer app.

Live, read-only: hashicorp/terraform#39262 requires license/cla from
integration 865473, reported green as a commit status by the CLA app. The
previous head refused it as unreported; this head no longer does, while
still naming the four required check runs that never ran there.

Refs kunchenguid#5344

* fix(document): Clarify accepted commit-status producer verification limitation

---------

Co-authored-by: firstmate-oss <firstmate-oss@kunchenguid.local>
…uid#5696)

* fix(bin): never offer a persistent secondmate for teardown

The return brief's "Landed, cleanup due" scan listed every state/*.meta
record carrying a pr= and a merge-notified marker without regard to kind, so
a secondmate record holding a relayed child's merged PR put the mate itself
up for "bin/fm-teardown.sh <mate>" cleanup. A secondmate is a persistent
worker, never landed work.

- bin/fm-afk-return.sh: skip kind=secondmate in the landed-cleanup scan.
- bin/fm-pr-check.sh: refuse to record pr= or arm a merge watch on a
  kind=secondmate record before any side effect; a PR reported on its routed
  status channel belongs to a task in the mate's own home, which arms its
  own watch.
- bin/fm-watch.sh: a merged result from a poll already armed on a secondmate
  retires the poll silently - no merge outcome, marker, or wake.

* no-mistakes(document): Document secondmate merge-watch and return-brief exclusions

* no-mistakes(ci): CI failed in an unchanged watcher-shutdown test whose three-second wait was sensitive to runner load. Increased the wait for both state- and home-deletion cases without changing watcher behavior. The full fm-watch-arm suite passed locally; syntax and diff checks passed
…unchenguid#5702)

* fix(bin): refuse unknown dash-leading args in public-posting fm-x scripts

fm-x-reply.sh collected any unrecognized argument into the positional
pool and took the first one as the reply text, so an invocation like
"fm-x-reply.sh <id> --followup --final <text>" posted the literal
string "--final" to X and silently dropped the real text.

Make argument parsing strict in every script that can post publicly:
an unknown dash-leading argument, a dash-leading request_id/task id, a
dash-leading option value, or a surplus positional now exits 2 with a
usage error before any config load, outbox write, or network call. Reply
text starting with '-' is still accepted via --text-file or stdin, and
--help is honored wherever it appears instead of becoming text (a --help
forwarded through fm-x-followup.sh would have counted as a posted
follow-up and mutated the link).

fm-x-link.sh and the fm-public-followup scripts already refuse unknown
arguments; fm-x-poll.sh takes none.

* no-mistakes(review): Refuse surplus follow-up text sources; drop post-ID help branches

* no-mistakes(document): Clarify reply and follow-up argument usage

* no-mistakes(review): Refuse dash-leading --text-file operands in fm-x-reply

* no-mistakes(document): Correct follow-up argument parsing comment

* no-mistakes(document): Document dismiss argument rejection in script header
…nchenguid#5701)

* feat(bin): latch the supervision host after repeated engine errors

Rung 3c-1 of the PR 5631 re-cut: the host copies the Pi branch's
broken-session policy. Two consecutive engine errors latch the session;
every away wake then reaches main with one supervision-host line for a
five-minute cooldown, after which one wake probes the engine, and each
failed probe doubles the cooldown up to one hour. A reported turn without
an engine error clears it. The latch is kept per main session, engine, and
model in state/.supervision-host-health, and the engine conversation now
uses the same main-session key, which includes the lock holder's process
identity so a recycled pid never shares either.

Lifted from the validated 5631 tree and adapted to main's away-only host:
the attended recovery line and attended cooldown pass-through are left for
the attended core, so a recovery is only logged.

* no-mistakes(document): Consolidate supervision-host latch documentation
…kunchenguid#5535)

* fix(bin): absorb routine second-mate progress while surfacing routed replies

Fixes kunchenguid#2959

A kind=secondmate task's status signal was never absorbable, so a healthy
mate's routine working: and paused: appends woke the primary every time.
signal_crew_provably_working now reads the mate's lines new since the
watcher's classified position: a decision, blocker, terminal outcome, note:,
correlation-marked line, or unknown verb still surfaces regardless of busy
evidence, while unmarked working:, paused:, and resolved: fall through to the
same provably-working absorb an ordinary crewmate gets.

* no-mistakes(review): narrow secondmate routine absorb to working and paused
* fix(bin): use gh-axi for the ship DoD draft check

* no-mistakes(review): use PR number not URL in gh-axi draft check
… only (kunchenguid#5520)

* fix(bin): drop status prose from the inactive-outcome dedupe identity

The inactive-outcome receipt fingerprint included the child's sanitized
last status line, so a persistent child appending routine prose after one
terminal outcome minted a fresh parent event per sentence. Bind the
identity to incarnation, task id, terminal state, and PR only, keeping
the last line in the record as status_head evidence.

Fixes kunchenguid#2960

* no-mistakes(document): note structured-only inactive receipt identity in regression coverage
…unchenguid#5707)

* feat(bin): record the supervision host's dialog mirror on Claude and Cursor

Add bin/fm-host-mirror.sh, the one owner of the supervision host's dialog
mirror file, cursor, lock, and feed, plus the main-session key it keys
entries to. The tracked Claude UserPromptSubmit and Stop hooks and the
Cursor beforeSubmitPrompt and afterAgentResponse hooks record the captain's
prompt and main's reply, only on a home with config/supervision-host, from
a genuine primary checkout, for the lock-owning session. The mirror lands
inert: writers record and nothing reads it yet; attended supervision on the
host is the later step that consumes the feed.

Codex, Grok, OpenCode, and omp have no writer here.

* no-mistakes(review): Scope mirror dedup to session, atomic appends, marker-inclusive caps

* no-mistakes(document): Clarify dialog mirror scope and remove duplicate contract details

* no-mistakes(document): Correct Cursor hook documentation for dialog mirror registration

* no-mistakes(review): Pass mirrored dialog text to jq via stdin

* no-mistakes(document): Clarify dialog mirror documentation and remove duplicate claims

* no-mistakes(review): Preserve internal dialog whitespace; drop mirror check and verified modes

* no-mistakes(review): Drop only identical mirror repeats; remove redundant chmod guard
… escalations are not repeated (kunchenguid#5731)

* fix(bin): retire check-row receipts on branch acks and report an unchanged situation once

* fix(bin): scope a branch acknowledgement's check-row receipt retirement to
  its granted sequences

The away posture lifts the attended partition's check/decision exclusions, so
a branch grant can name check-kind rows - but the branch-actor ack still
assumed check rows were main-only and skipped every receipt scan. The queue
row was consumed while its terminal-outcome .pending receipt stayed behind,
and each inactive-reconcile cadence scan re-queued the same fingerprint. In
the first real away window on the supervision host that re-escalated one
unchanged held-PR situation on every cycle (~1,734 of 4,149 outcomes).

A branch ack now scans inactive-outcome and inactive-reconcile receipts and
commits secondmate stall receipts against exactly the sequences in its
eligible-row snapshot - the same rows it consumes - instead of none. Attended
grants still name no check row, so the scans find nothing.

* fix(bin): store a repeated captain verdict as routine while the task's
  durable situation is provably unchanged

fm-branch-outcome.sh append computes a mechanical situation key per captain
row - metadata bytes, captured status-log endpoint and identity, live
crew-state verb, worktree head - and anchors it in
state/.<task>.branch-captain-key. A later captain verdict whose recomputed key
matches is stored as routine with "unchanged since seq <N>:" prefixed to its
summary, so one situation escalates once until something provably changes. A
task with no readable status ledger is never demoted, an unreadable record
fails toward reporting, and teardown removes the sidecar with the task's
other branch records. The append-only store schema is unchanged.

This covers both hosts: the Pi supervision branch and the supervision host
both funnel reports through append.

* docs: check rows are main-owned only while attended; the away posture grants
  them to the branch, whose ack retires their receipts exactly

* test: the away-flood reproduction as a regression test (branch ack retires
  the receipt and later scans stay quiet), store-level dedupe coverage, and a
  branch-ack secondmate stall receipt case

* fix(bin): restore the secondmate child devin-config cleanup path

The branch-captain-key sidecar addition mistyped the sibling entry as
.$child_id.devin-config.json, so a forced secondmate teardown would have
stopped removing each child's real <id>.devin-config.json. Restore the
original path and add a behavioral test that stops the child sweep mid-loop
on a refused close, proving the cleaned child's devin config and captain
anchor are both removed while the unconsumed child's records are retained.

* no-mistakes(review): Key captain dedupe on the covered wake rows' fingerprint

* no-mistakes(review): Drop captain-key demotion; prove one escalation on both surfaces

* no-mistakes(review): Drop unrelated teardown test; cite both receipt test files

* no-mistakes(document): Docs already match branch-ack check-receipt retirement
…oorbell (kunchenguid#5664)

* fix(calm): deliver Claude-bound operational input as a record-backed doorbell

Claude Code 2.1.280 removes U+2063 from every submitted prompt, so a typed
operational envelope reaches a Claude Code primary as plain text. The away
daemon now writes the envelope to a record under state/operational-inbox and
types only a plain doorbell naming it; the /afk return check and the Calm mod
recognize the doorbell only when that record holds a current envelope. Marker-
preserving harnesses keep the typed envelope. The live Calm guard accepts the
2.1.280 module-load log line, drives the doorbell, and asserts thinking stays
hidden.

* no-mistakes(review): Fix operational record retention at 7 days and document prune limit

* no-mistakes(document): Point Calm bounds at 2.1.280 evidence; fix afk-exit comment

* no-mistakes(lint): Pick newest Calm e2e transcript without parsing ls

* docs(calm): add a minimal turning-Calm-on step for Claude Code

* fix(spawn): deliver the Claude launch brief as a record-backed doorbell

Claude Code strips U+2063 from the launch-prompt argument too, so a
worker's launch brief arrived with its operational marker removed.
Publish the brief as a record in the receiving home's operational
inbox - a secondmate's own state, not the primary's - and pass only
the printable doorbell naming it, falling back to the typed envelope
when the record cannot be published so the brief body still delivers.

Unwrap doorbell-carried digests in the daemon digest tests that still
read the raw send log under the claude pin, and update the documented
bounds now that launch briefs hide like the other operational rows.

* test(spawn): cover a secondmate's launch-brief record landing in its own home

The record-backed doorbell resolves its state through the receiving
pane's home, so prove a claude secondmate launch publishes into the
seeded secondmate's operational inbox and never leaks a record into
the primary's.

* no-mistakes(review): Pass primary harness to daemon, tighten retention, refresh verdicts

* no-mistakes(review): Prune operational records by exact seven-day elapsed age

* no-mistakes(review): Batch record pruning so large inboxes still expire

* no-mistakes(review): Refuse Claude spawn when brief record cannot publish

* no-mistakes(review): Drop thinking probe from Claude Calm live test and docs

* no-mistakes(review): Record dated Claude Code 2.1.282 reproduction evidence

* no-mistakes(document): Clarify operational doorbell documentation and record expiry

* no-mistakes(document): Correct AFK escalation carrier guidance

* no-mistakes(review): Describe operational record retention as about seven days

* no-mistakes(document): Clarify Calm delivery and operational record retention

* no-mistakes(review): Remove out-of-scope Calm launch guide from Claude docs

* no-mistakes(document): Document Claude launch-brief delivery and refusal

* no-mistakes(document): Correct stale operational-input documentation

* no-mistakes(ci): Fixed the stale Claude trust test to verify that worker and secondmate launches deliver readable, record-backed briefs instead of expecting brief paths in their commands. Annotated the daemon’s output variable for ShellCheck without changing behavior. The affected tests, daemon tests, ShellCheck, and diff check pass locally

* no-mistakes(ci): parse rebased Claude launch after trailer hook prefix

* no-mistakes(review): Trust launch-brief record and restore thinking bound doc

* no-mistakes(review): Parse final Claude launch statement; drop Stop-hook docs

---------

Co-authored-by: Mike Sewell <maikunari@protonmail.com>
Co-authored-by: no-mistakes <no-mistakes@localhost>
kunchenguid#5583)

A host-local relaunch rewrote only the far endpoint, so this home kept the old harness, model, and effort, and appending those keys after pr= broke pull-request poll authentication.
…ound (kunchenguid#5516)

tests/fm-watch-triage.test.sh finishes in about 434s alone and about 698s
under CI load, so the 900s bound the changed-suite runner applies produced
a false timeout under ordinary concurrent validation. Raise the automatic
bound to 1500s, which keeps every measured script under it while staying
below the 30-minute normal CI tier so a genuinely hung script still fails
here with its output before the job cap cancels the lane.

Fixes kunchenguid#3869
Refs kunchenguid#3565
…kunchenguid#5728)

* Fix nested watcher lock reclaim

* no-mistakes(review): Elect a single steal-mutex reaper and bound arm TERM wait

* no-mistakes(review): Reclaim self-held steal mutex and unify autoarm steal reaping

* no-mistakes(review): Resume own interrupted steal reap from its tombstone
…nguid#5710)

* test: hold the back-to-back boundary close on the host's own clock

test_park_boundary_holds_under_back_to_back_closes assumed two engine
turns fit in the ~16s pre-refusal window and that the stub finished a
turn in 3s. Under load the stub's real drain, report, and
acknowledgement take ~13s, so the turn either died at its bound (which
hands the wake to main, no boundary line) or the second close landed
past the window and the fixture failed while the boundary held. 3
failures in 5 runs at a load average near 11.

Hold the first turn on a release file instead: once the engine is in
flight, a second close is appended mid-turn and the turn is released as
the refusal window opens (park bound minus turn bound and grace, read
off the host's own start record). The queued close can then only wait
for the boundary on any machine speed, which is what the test asserts:
the boundary line ends the output, the demo.status row stays queued for
main, and no second engine turn ever starts. A host too loaded to start
the turn at all hands the first close to the same boundary exit.

After: 12/12 at load ~15-42.

* no-mistakes(review): Print boundary test deadline as a decimal integer

* no-mistakes(review): Hold boundary test turn on a FIFO, require full sequence

* no-mistakes(review): Remove stray before/after supervision-host test copies

* test: hold the late close's render until the refusal window opens

The boundary recheck test's node shim slept a fixed 10s, which assumed
the first close was read before the host's refusal window opened. Under
load the close arrived after the refusal check, so the host correctly
refused it before the successor started and the render snapshot never
appeared. Block the wake-prompt render on a FIFO released at the
refusal-open instant read from the host's own start record, so the
pre-turn recheck must refuse on any machine speed.

* no-mistakes(review): Derive minimal park bounds and refresh supervision-host shard hint

* no-mistakes(review): Drive park-boundary tests from a seam-gated host test clock
* fix(bin): stage remote home clones before publishing them

A remote home provision cloned the code root directly into the public
FM_HOME path while rollback() claimed rm -rf of that same path on any
failure. Bash defers trapped signals past a foreground child, but any
other cleanup or lifecycle path that removes the home directory races
the live clone's object copy, producing the CI flake "fatal: failed to
copy file to .../.git/objects/...: No such file or directory".

Clone into a private staging directory beside the home and publish with
an atomic rename once complete, so no cleanup can remove a directory a
live clone is still writing; a home that appears mid-provision now dies
cleanly instead of inheriting torn state. The regression coverage holds
a real clone mid-copy, removes the public path, and requires the
provision to finish and publish intact.

* no-mistakes(review): Prove home ownership by sentinel and hold only a live clone

* no-mistakes(review): Assert raced provision publishes a complete, intact clone

* no-mistakes(document): Document remote home staging and publication safety

* no-mistakes(lint): Fix ShellCheck warning in clone integrity assertion

* no-mistakes(document): Clarify remote home publication and rollback guarantees
* fix(control): keep a relaunched Pi worker's herdr pane status authority alive

Defect: after `bin/fm-control.sh <id> relaunch` (observed live on a herdr
Pi crewmate whose pane read idle while it ran its validation pipeline),
the pane froze at whatever its previous agent had last reported.

Cause, measured on herdr 0.9.1 against a real Pi: a pane has one status
authority, and for Pi with its integration installed that authority is
the lifecycle hooks, so herdr also skips screen detection for the pane.
In the crew shape the registration outlives its agent process (upstream
issue kunchenguid#4115; docs/herdr-backend.md "Restart and liveness behavior"), and
herdr applies only reports carrying the session identity it bound. A
replacement started fresh in that pane reports a NEW session, so its
state reports are ignored and the pane stays frozen. Nothing from
outside repairs it: `pane report-agent-session` and `pane report-agent`
for `herdr:pi` are accepted (rc=0) without being applied unless the
reporter is the registered pane agent, and `pane release-agent` on the
stale record changes nothing.

Fix: a relaunch preserves the binding instead of fighting it. The launch
owner reads the session reference the endpoint's own runtime recorded
(`fm_backend_herdr_pane_agent_session_ref`) and passes it back as Pi's
own `--session <path-or-id>` (`relaunch_resume_args`;
`fm_control_relaunch_resume_flag` owns which adapters and which
registered-agent labels qualify). That is the same reference herdr
itself resumes Pi panes with after a server restart, and the resumed
session's reports land again, which the live check confirmed: the pane
returned to working while the replacement worked and idle when it
settled, on the same session identity.

Safety: relaunch-only (a fresh spawn binds nothing), herdr-only (the one
adapter that records a per-pane session), Pi-family only, and only when
the registration's own agent label matches - so no other adapter's
conversation can be handed to a Pi launch. An unreadable, missing, or
malformed reference degrades to exactly the fresh-session launch that
existed before. No lifecycle, liveness, isolation, or merge guard is
touched, and an empty result leaves every non-Pi launch byte-identical.
`resume` remains a refused verb; docs/agent-control.md and the
harness-adapters references are corrected where they claimed Pi had no
verified resume form at all.

* no-mistakes(document): docs: correct relaunch session-authority ownership and skill paths

* no-mistakes(document): docs: correct stale control-plane ownership claim

* no-mistakes(document): docs: drop unverified Herdr restart resume claim

* no-mistakes(test): Added offline Herdr Pi session-authority relaunch coverage

* no-mistakes(document): Document Herdr Pi relaunch session continuity

* no-mistakes(ci): The failing remote relaunch test tried to arm a PR poll for a secondmate, which `fm-pr-check.sh` correctly refuses. Removed that invalid test scenario; the remaining remote relaunch tests pass, and `git diff --check` is clean
…nchenguid#5758)

Main has been red since fm-pr-check.sh began refusing to arm a merge poll
on a kind=secondmate record (kunchenguid#5696): the relaunch-ordering case in
tests/fm-remote-secondmate-relaunch.test.sh armed its fixture through that
entry point and could no longer be set up.

The ordering guarantee still matters: a secondmate record armed before the
refusal can legitimately carry a trailing pr=/pr_head= identity block until
the watcher retires it, and fm-remote-secondmate-relaunch.sh must still keep
that block last when republishing harness/model/effort. Seed the fixture the
way such a record was really written - pr= appended last to the meta, then
the poll artifacts published through the same
fm_pr_poll_prepare/fm_pr_poll_publish_prepared pair fm-pr-check.sh uses, a
pattern tests/fm-pr-check-security.test.sh already follows - and drop the
now-unused fake gh fixture. The kunchenguid#5696 refusal itself stays pinned by the
security suite's secondmate-record case.
…id#5748)

* feat: run attended supervision on the host for Claude and Cursor

On a home opted into config/supervision-host with a Claude or Cursor
primary, the supervision host now takes the attended wakes the Pi branch
would take: routine outcomes stay off main, and a captain outcome wakes
main once with a branch-outcome line and waits in the drain's new
BRANCH OUTCOMES section until main acknowledges it with mark-processed.

- The offer rule moves into branchOfferForWake, shared by the Pi watcher
  and the host through bin/fm-branch-dispatch.mjs offer.
- The host feeds the dialog mirror at the head of each attended wake and
  passes a close through unchanged when it is main-only, the engine or a
  tool is missing, the primary has no verified mirror, the main session
  cannot be identified, or the session is cooling down.
- The drain presents captain outcomes first, one line per task, never
  behind older routine outcomes, and collapses routine overflow into a
  count that is marked read.
- The return advances the store's read cursor through the away window
  once the brief has rendered, so the first drain does not replay it.
- The branch prompt's mirror wording is host-neutral, and the rule to
  report what main must act on as captain, once per unchanged situation,
  applies only to the attended posture on the host.

* docs: record the attended supervision host live check

* no-mistakes(review): Present pre-window unread outcomes and contiguous captain prefix

* no-mistakes(review): Return brief presents every row it marks read

* no-mistakes(review): Return brief lists every unread outcome in one list

* no-mistakes(review): Keep return list in store order and gate cursor failures

* no-mistakes(review): Make the drain the only branch-outcome presenter after return

* no-mistakes(review): Gate return on drain outcome failures; byte-count outcome budgets

* no-mistakes(review): Gate drain on projection failures; UTF-8-safe byte cuts

* no-mistakes(review): Fail drain without jq; hand unreadable prompt mirror to main

* no-mistakes(document): Correct supervision-host return and drain documentation

* no-mistakes(review): Recheck attended offer at turn start; honest failed-drain brief

* no-mistakes(document): Correct supervision-host posture and drain documentation

* no-mistakes(document): Documentation remains accurate for attended supervision
…unchenguid#5753)

Each '# shellcheck source=' directive makes ShellCheck's external-source
traversal expand that library's whole transitive graph again at the site.
fm-pending-reply-lib carried three directed lazy sources of fm-wake-lib and
two of fm-parent-channel-lib on identical per-call re-source sites, so one
file analysis peaked above 4 GiB and every caller (fm-watch, fm-teardown)
inherited the multiplier - the root cause of the PR kunchenguid#5732 Lint 1 OOM kill.

Keep the runtime '.' commands byte-identical: the lazy re-source under
'local STATE FM_WAKE_QUEUE FM_WAKE_QUEUE_LOCK' is real behavior. Drop the
duplicate directives so each library expands once per unit, and drop the
tmux/classify directives since classify already arrives through the kept
fm-wake-lib expansion and no tmux symbol is referenced here. The directive
above the lib-dir assignment is kept - it binds the bin/ prefix so the
undirected sites still resolve without SC1091.

Measured peak RSS, ShellCheck 0.11.0 -x on Linux arm64:
  bin/fm-pending-reply-lib.sh  4.06 GiB -> 1.96 GiB, zero findings
kunchenguid#5773)

* fix: split bash 5.2 sibling $() in recovery mint and delivery log

Sibling command substitutions on one line can empty a recovery generation
under bash 5.2 when a CHLD trap is set. Mint pid/epoch sequentially, refuse
empty tokens before write, and clean delivery fields before printf.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: split bash 5.2 sibling $() in recovery mint and delivery log

Sibling command substitutions on one line can empty a recovery generation
under bash 5.2 when a CHLD trap is set. Mint pid/epoch sequentially, refuse
empty tokens before write, and clean delivery fields before printf.

Co-authored-by: Cursor <cursoragent@cursor.com>

* fix: keep recovery mint failure semantics after sibling $() split

Remove the new pid/date refusal and grammar guard so a mint miss still
yields a grammar-valid token and a durable wake row, matching accepted
review intent. Drop the fake-failing-date case that locked in the refuse.

Co-authored-by: Cursor <cursoragent@cursor.com>

* no-mistakes(document): Point recovery-mint hazard comment at its regression test

---------

Co-authored-by: Cursor <cursoragent@cursor.com>
…unchenguid#5790)

Fixes kunchenguid#4756

The voice status reader in bin/fm_voice_records.py reports each
worker's state from the last non-blank line of its status log. When a
worker appends a status line and then a line of plain prose, the
reader reported "note" with the prose line instead of the declared
state, diverging from bin/fm-classify-lib.sh's shell scan.

Scan back through the tail for the newest line whose prefix is a
single lowercase verb-shaped word (letters and hyphens), and report
that event's verb instead of always taking the last line. An
unrecognised verb-shaped prefix still reports "note" rather than
letting an earlier recognised line answer for it, and free text with
no colon is skipped as prose. When the tail holds no such event, the
last line is reported exactly as before.
tiago-peixoto and others added 28 commits October 22, 2026 11:21
…nd an opt-in flag (kunchenguid#7634)

* refactor(bin): share the branch-to-PR lookup through fm-pr-lib

* feat(bin): notify owned PR health changes behind an opt-in flag

* no-mistakes(review): drop status-line ownership, key checks per name, remove PR wait rule

* no-mistakes(review): rediscover the branch PR once the cached one is final

* no-mistakes(review): refuse retired re-enrollment, require marked checks, verify owned records

* no-mistakes(document): note shared branch-to-PR lookup in scripts table

* fix(bin): notify reported failures when no checks are required

* no-mistakes(document): inventory PR health flag and state, note new pr-forge member
* Isolate real Rex tests with private servers

* no-mistakes(review): Trap Rex cleanup early and prove passive-read autostart guard

* no-mistakes(document): Consolidate Rex test isolation documentation
* fix(bin): report an outdated bootstrap tool instead of a missing one

detect_local_tools printed "MISSING:" for a treehouse without --lease, a
no-mistakes below 1.46.0, a gh-axi below its floor, and an incompatible
tasks-axi or quota-axi, so a supervisor read an installed tool as absent
and took the first-install remediation again.

Report those as "OUTDATED:" with the installed version, the requirement
and the tool's own upgrade command: treehouse update, no-mistakes update,
gh-axi update and quota-axi update. tasks-axi ships no self-updater, so it
keeps its install command. "MISSING:" stays for absent tools only, and
lavish-axi keeps PRESENTATION_UNAVAILABLE with its text fallback.

Update the bootstrap-diagnostics handler, the AGENTS.md section 13 trigger
list and the operator diagnostics in docs/configuration.md, and cover
absent, below-floor and at-floor for every floored tool in
tests/fm-bootstrap.test.sh.

* no-mistakes(document): Documented distinct bootstrap tool diagnostics

* fix(bin): reuse the tasks-axi check's own probe results

Rendering the tasks-axi OUTDATED requirement text ran the two `--help`
capability probes again after the compatibility check had already run
them, including when session start handed bootstrap an incompatible
verdict. The check now records the reason behind its verdict - the
version floor, or the capability probes the build fails - and reuses it,
both within the process and across the existing one-hop handoff, so
naming a missing capability costs no second probe pass. A build below the
floor is answered by its version alone and never pays for capability
probes it cannot use.

The bootstrap tests that simulate a tool as absent removed it from the
fake bin directory while bootstrap kept searching the fallback base PATH,
so a real host install could satisfy the lookup instead of the expected
MISSING line. Those cases now hide the tool from the base path as well,
and a new case pins the contrast between a bare and a curated fallback.

Verified: the touched bootstrap suite passes, and its probe-count and
capability assertions fail against the pre-fix scripts. Run with a
fallback base PATH carrying real installs of the absent tools, the
pre-fix suite fails on "absent treehouse reports a missing tool" while
the fixed suite passes every case.

* no-mistakes(document): Documented missing versus outdated tool diagnostics

* no-mistakes(document): Refresh outdated-tool documentation

* no-mistakes(review): Reuse shared version parser for outdated diagnostics

* no-mistakes(document): Correct outdated-tool diagnostics documentation

* no-mistakes(document): Align toolchain upgrade documentation

* fix(skills): keep main's bootstrap-diagnostics description and only add OUTDATED
…e verbatim match (kunchenguid#7530)

* Clarify ahoy legacy startup exclusion

* Fix quoted payload code span
…unchenguid#7637)

* fix(timeout): bypass shell builtins when invoking external runners

* no-mistakes(review): Remove redundant slashless runner resolution from external timeout
…urable (kunchenguid#6094)

* fix: count answered captain holds in the Done archive at the completion gate

Done retention moves closed markdown rows into the configured Done archive,
which tasks-axi show never reads, so verify, complete, and scout teardown
refused an answered captain hold once retention archived it. The completion
gate now reads the newest archived block for an attested entry, under its
exact or legacy identity, and accepts it only through a recorded resolution.
Unanswered archived closes and holds found nowhere still refuse.

* no-mistakes(document): document Done-archive durability in captain-hold verification record

* no-mistakes(ci): Fixed the selected Greptile findings in bin/fm-captain-hold.sh (all in the Done-archive read path added by this PR). code_change_needed=true. ci-3 (Archived reads can hang): task_show_archived() now bounds the replayed archive `tasks-axi show` with the same read bound the live backlog read uses (FM_BACKLOG_ROW_TIMEOUT_SECS via fm_run_timed, sanitising a non-positive/padded value to 10s, mirroring fm_backlog_row_show). A bound hit is treated as 124, not absence: task_show_archived returns 124 and prints a loud reason; archived_entry() propagates 124; verify_entry_durable() exits 124 on the archived-read bound (alongside its existing resolve 124 handling); verify_hold_durable() exits 124 rather than reading the row as absent. So verify can no longer hang and complete can no longer stay blocked while holding the origin metadata lock. ci-4 (User-level archive setting ignored): added captain_markdown_archive_setting(), which resolves the [markdown] archive key with tasks-axi's own source precedence — the backlog root's .tasks.toml first, then $HOME/.tasks-axi/config.toml — before the existing default of done-archive.md beside the backlog; a relative value still resolves against the backlog root, which I verified empirically matches tasks-axi (root config wins over home config; a relative home-config value resolves against the backlog root; default stays data/done-archive.md). tasks-axi exposes no command for the archive path, so re-parsing with matched precedence is the available approach. Added test_archived_answer_in_a_user_configured_archive_satisfies_the_gate, which sets the archive only in a fixture $HOME/.tasks-axi/config.toml and drives the whole hold→answer→retention→verify/complete/teardown flow with that HOME, asserting the answered call archived to the relocated location still satisfies the gate. ci-5 (repeated archive scans, P2): left unchanged per the instruction's opt-out. A clean reuse of the row archived_entry already found is not small: archived_entry runs inside a command substitution, so its TASK_SHOW_OUTPUT dies with the subshell and cannot be handed to verify_hold_durable without restructuring both to pass the loaded row through the current shell. The growth is bounded (O(archive size) per attested entry) and P2, so hardening it would add more machinery than the fix warrants. Unanswered archived closes and holds found nowhere still refuse (existing test coverage retained). Verification: `bash -n` clean; pinned ShellCheck 0.11.0 (`shellcheck -x bin/fm-captain-hold.sh`) reports 0 findings; tasks-axi archive-precedence behaviour confirmed by direct experiment. The full fm-captain-hold-lifecycle.test.sh suite (which exercises both the pre-existing archived-gate test and the new user-config test) is slow due to beads spawns and was still running at report time; recommend confirming it green before merge. ci-1/ci-2 need no code change (action_required fork workflow runs awaiting maintainer approval), per the recorded user decision

* no-mistakes(document): Document Done-archive reads under backlog row timeout

* docs: say only the archive row replay is bounded, not the archive scan

* no-mistakes(document): Move Done-archive rule to the complete section
…kunchenguid#7627)

* fix(bin): optionally read a PR with no CI checks as unverified, not checks green

nm_ci_checks_state maps the ci log marker "no CI checks reported - still
monitoring" to green, the same verdict as "all CI checks passed". A pull
request that no check ever ran against therefore surfaced as "checks green: PR
ready for review" (kunchenguid#3216).

Add a no-checks verdict for that marker, returned only in a home that creates
config/ci-no-checks-not-green. The run-step consumer reports working with a
detail naming the real condition, and the status-log override no longer
promotes a crew's own "checks green" report while that marker is the latest.
Without the flag every reading is unchanged.

Co-authored-by: Harlan Do <hieudtr8@gmail.com>

* no-mistakes(document): document ended-monitor reading under the zero-checks flag

* fix(bin): read a PR with no CI checks as unverified on every home

The ci log marker "no CI checks reported - still monitoring" now always
yields the no-checks verdict, so a pull request that no check ran against is
reported as working instead of checks green (kunchenguid#3216). The opt-in flag, its
docs section, and its skill entry are removed, the default-path test now
expects working, and the usage header and architecture note describe the
no-checks reading.

---------

Co-authored-by: Harlan Do <hieudtr8@gmail.com>
… test root-safe (kunchenguid#7650)

* no-mistakes(review): correct the counter-reset log line, the sentinel comment, and the root proof

Three findings the review step recorded but never applied, all in this branch's own change.

The clean-rollback branch of the counter-reset guard logged "cap state rolled back", copied
from the sibling marker-write branch. That is wrong here: the cap marker was already written
before this block and _wedge_cap_rollback only truncates the counter, re-arms the since-file
and clears write tracking - it never clears the markers. The window therefore stays silenced
for FM_CAP_HORIZON_SECS, so the one operator-facing line must say that rather than claim the
cap state was rolled back. The rollback behaviour is unchanged and no marker-clear call is
added: the marker is deliberately kept so the window stays quiet.

The sentinel short-circuit comment opened "from either route that has to defend cap state"
and then enumerated three routes. Wording only.

The new regression case injected its fault with chmod 444, which does not restrict root, so
under root the guard would never fire and the case would fail on its own exit-code
assertion. It now skips as root the way this suite's other mode-based faults do. The
assertions are untouched: as the ordinary user the case still proves the guard.

* no-mistakes(review): name both ways a capped window leaves the silence in the reset log line

The tier-0 message for a refused post-fire escalation-counter reset says the cap marker
stands, so the window stays silent until its horizon elapses. That names one release route
where there are two: wedge_episode_end drops the marker through _wedge_cap_markers_clear, so
any episode-ending route ends the silence early, and docs/configuration.md records deleting
the marker as the operator's way to re-engage a capped window at once. This tier writes no
sentinel, so the marker is the only thing left to remove.

The message now names the horizon, an episode-ending route, and the operator remedy, in one
line in the same shape and position as its sibling tier-0 messages. The rollback behaviour,
the marker handling and the tier are unchanged.

* no-mistakes(document): correct root-skip comment's mode-bit precedent in wedge-cap test
* fix(bin): resurface wakes appended while a handling successor is parked

The supervision host parks on the handling successor it starts for a wake
its engine handled, and a handling successor never resurfaced recovery, so
a row another script appended during the park (a captain inbox note from
the Basecamp integration) sat queued until an unrelated close - four hours
in the observed case.

A handling successor now records the newest queued sequence at its start
and closes once with the recovery wake while a newer row is still queued;
a newer row already consumed only advances that mark, so the predecessor's
delivered episode is still never re-announced.

Follow-ups, not built here:
- A row appended in the short window between a handled close and its
  successor's start (about a second) is below the successor's mark; if the
  engine handles that close without claiming the row, the row waits for the
  next close. Closing that window needs the host to treat rows left queued
  after a handled attended turn as main's, which is a separate host change.
- After a machine reboot the primary session does not restart on its own,
  so queued wakes wait until it is resumed by hand. Either auto-resume the
  primary session after boot, or alert the owner when the primary session
  is down while wakes are queued.

* no-mistakes(review): Close handling-successor startup wake race

* no-mistakes(test): Stabilize parked-host inbox wake regression timeout

* no-mistakes(document): Clarify handling-successor wake continuity guarantees

* no-mistakes(ci): Stabilized the flaky remote secondmate lifecycle test by extending the auto-relaunch watcher deadline from ~30 to ~60 seconds. The CI runner exceeded the old deadline by about 3 seconds; the changed production branch was not reachable in this fixture. `bash -n` and `git diff --check` pass. The full test was attempted but exceeded the 600-second local execution limit before reaching this case

* no-mistakes(ci): Fixed flaky concurrent Herdr recovery testing by retrying only documented session-lock contention after both competitors finish. Cleanup now handles fixture-owned read-only hooks. Targeted E2E passed; bash syntax and diff checks passed. Shellcheck reported only existing SC1091 source-following notices

* no-mistakes(test): Preserve successor wake boundaries across handoffs

* no-mistakes(test): Revert successor boundaries and drain teardown fixture wake

* no-mistakes(test): Notify main when downtime handback publication fails

* no-mistakes(test): Revert downtime handback notification changes

* no-mistakes(document): Correct stale supervision wake routing documentation

* fix(bin): stop the supervision host deadlocking and re-announcing handed-back closes

Two defects in this branch's handling-successor changes surfaced as the
"Behavior portable serial 2" shard hanging at its 30-minute limit.

A TERM that lands while the host settles a handled turn's successor handoff
left the host holding the wake-queue and recovery-marker locks; its exit
cleanup then waited forever on the queue lock it still owned. The cleanup now
releases those locks if this process owns them before releasing its grant.

When an accepted close turns main-only at its turn, the host hands it back and
leaves the successor running, but republished the episode as pending
downtime, so that successor re-announced the same close and exited. The
handback now republishes the episode as already announced; a later durable
append still opens a pending episode the successor recovers.

* no-mistakes(test): Park on successor after handling settlement failure

* no-mistakes(test): Use tab-delimited handled-settlement failure logs

---------

Co-authored-by: Caleb LeNoir <calebl@users.noreply.github.com>
* fix(bin): keep accented letters in pending-reply request summaries

fm_pending_reply_summarize deleted every byte outside printable ASCII, so a Portuguese summary lost its accents: "não faça a correção" was stored as "no faa a correo". It now removes control characters only. The 120-character cap counts UTF-8 characters from lead bytes, so it never splits a letter under any caller locale, including the C locale.

* no-mistakes(document): Documentar preservação de acentos nos resumos

---------

Co-authored-by: Alexandre Custodio <alexandre.custodio@soraschools.com>
…id#7149)

* Add explicit OpenRouter Jev dispatch resolution

* no-mistakes(document): Clarify dispatch documentation and remove stale duplicates

* chore: exclude local Stele binding from PR

* no-mistakes(document): Correct dispatch help and never-send documentation

* ci: retrigger CI after a timed-out test shard

* no-mistakes(document): Correct dispatch architecture documentation ownership
…imits (kunchenguid#6983)

Opt-in: fm-watch.sh publishes state/lane-capacity.json via bin/fm-capacity.sh --publish only while config/lane-capacity exists; adds beat pressure and keyed limits to fm-capacity.sh. Unconfigured fm-capacity output unchanged.
…#5359)

* fix(bin): transport contribution-input JSON through slurpfile, not argjson

bin/fm-fleet-snapshot.sh --contribution-input passed the full backlog and
task-record JSON to jq through --argjson. A single --argjson argument is
capped by the kernel's MAX_ARG_STRLEN (131072 bytes on Linux), so a large
enough backlog made the kernel refuse to exec jq while the script still
exited 0 with a broken result.

Transport both documents through temporary files and --slurpfile, matching
the pattern the default snapshot path already uses, and propagate a
non-zero exit when that jq invocation fails.

Fixes kunchenguid#5349

* test: narrow contribution-input regression to final assembly failure

The ARG_MAX transport is already fixed on main by kunchenguid#6830, so keep only the regression proving a failing final jq assembly exits non-zero.
* Fix Rex secondmate session lifecycle

* no-mistakes(review): Retire Rex secondmate sessions under the owning home identity

* no-mistakes(review): Limit Rex FM_ROOT label override to secondmate launches

* no-mistakes(document): Clarify Rex secondmate grouping and lifecycle documentation
…gins, and exit (kunchenguid#7336)

* feat(opencode): add OpenCode 2 support with version-gated launch and plugins

OpenCode 2 dropped the v1 server-plugin hook API, so the five fm.primary.*
plugins now export a default definition with an id and a setup function
alongside their legacy OpenCode 1 server export, backed by a new shared
lib/fm-opencode2.js helper; the turn-end guard, watch-arm, cd-check,
pretool-check, and sessionstart-nudge install their behavior from setup(ctx)
on OpenCode 2 and keep the server entrypoint for OpenCode 1.

fm-spawn probes the resolved opencode executable's --version once and branches
the launch, model flag, reasoning config, exit key, and busy-state plugin off
the detected major. A parsed major >= 2 takes the OpenCode 2 shape (no --model,
the model pinned through OPENCODE_CONFIG_CONTENT, a raw Ctrl-C exit, the
session.execution.* busy plugin); OpenCode 1.x and any undetected or
unparseable version fall back to the 1.x shape (--model, 1.x config, /exit)
everywhere. The worker busy-state plugin is always provisioned, but OpenCode 1
keeps the pre-port session.status/session.idle plugin so its busy-state wiring
is unchanged, and only a major >= 2 gets the session.execution plugin. Adapter
and supervision docs, OpenCode 2 live fixtures, and regression tests for the
unknown->1.x launch path and the version-gated busy-plugin shape are included.

* no-mistakes(review): gate OpenCode 2 busy plugin turn-end touch on notifyTurnEnd

* feat(opencode): launch detected OpenCode 2 workers with --standalone

OpenCode 2's default launch attaches to the shared background service, which
hosts plugins with the service's environment and ignores the pane's
OPENCODE_CONFIG_CONTENT. A private server keeps per-worker model, permissions,
FM_HOME, and lock ancestry correct. 1.x and undetected versions are unchanged.

Ported from kunchenguid#5143.

Co-authored-by: Girik Bajaj <bajajgirik2010@gmail.com>

* fix(opencode): resolve the OpenCode 2 project location and treat every terminal turn as a turn end

OpenCode 2.0.x hands plugins the project only as ctx.location.project.directory
and takes ctx.session.prompt({ sessionID, text }), so the V2 setup path never
found its root or delivered a prompt. Interrupted and failed turns now end a
turn like a succeeded one, while a server shutdown does not.

Ported from kunchenguid#5143.

Co-authored-by: Girik Bajaj <bajajgirik2010@gmail.com>

* test(opencode): add a token-free OpenCode 2 native plugin fixture to the live e2e

A private opencode serve plus a loopback provider proves the five primary
plugins load and act on OpenCode 2: startup nudge, cd and watcher-redirection
guards, turn-end guard, watcher arming across succeeded, interrupted, and
failed turns. The fixture declares the complete model capabilities object,
which OpenCode 2.0.11 requires and 2.0.22 accepts. FM_OPENCODE_LIVE_BINS runs
it across several OpenCode 2 executables. The credentialed half now picks its
run flags and session tables by detected major, so it keeps working on 1.x.

Fixture ported from kunchenguid#5143.

Co-authored-by: Girik Bajaj <bajajgirik2010@gmail.com>

* feat(opencode): send one Enter when an OpenCode 2 launch leaves the brief unsent

OpenCode 2.0.11 prefills the --prompt brief without submitting it, while 2.0.22 submits it.
After launch, fm-spawn waits for the worker's busy-state plugin to report the turn start, sends one Enter only if it has not started and the composer is not empty, and fails the spawn if the turn never starts.

* docs(opencode): document the OpenCode 2 launch, delivery gate, and standalone primary homes

Records the 2.0.11 and 2.0.22 live evidence for the native plugin fixture and the post-launch brief delivery gate.

* no-mistakes(review): Trim OpenCode 2 adapter shapes and harden Ctrl-C exit

* no-mistakes(review): Record OpenCode 2 live seatbelt and exit evidence

* no-mistakes(test): Document OpenCode 2.0.22 wide-pane sidebar exit refusal limitation

* no-mistakes(document): Refresh OpenCode 2 exit-key and seatbelt payload docs

* test(opencode): verify OpenCode 1.x live with the dual-export plugins

Pin the live e2e TUI panes to the resolved opencode executable, and record
OpenCode 1.15.7, 1.18.20, and 1.18.31 evidence for plugin loading, the
turn-end guard, watcher continuity, and 1.x worker launch and exit.

* no-mistakes(review): Defer OpenCode 2 Enter on unknown composer; widen turn-end docs

* no-mistakes(document): Document OpenCode 2 delivery gate unknown-composer test outcomes

---------

Co-authored-by: Niranjan <nniranc@gmail.com>
Co-authored-by: Girik Bajaj <bajajgirik2010@gmail.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
…t the most recent group member (kunchenguid#7662)

* fix(bin): resolve the tmux container from the caller's own session, not the most recent group member

* no-mistakes(test): Fall back when the $TMUX session id is stale

* no-mistakes(document): Document tmux container choice under grouped sessions
…er (kunchenguid#7647)

* fix(bin): read a fully dim Cursor placeholder as an empty composer

cursor-agent 2026.10.19 draws the composer glyph and its idle placeholder
as one SGR 2 run with no reverse-video cell, so ghost stripping empties the
row and the empty-content guard returned unknown. Relaunch and exit then
refused on every idle Cursor pane on tmux.

When a styled bare row strips to empty and the plain row is an agent glyph
followed by an anchored idle placeholder, read it empty. Bright typed text,
other dim text, and unstyled rows keep their existing verdicts.

Fixes kunchenguid#7413

* docs(verification): record the fully dim Cursor placeholder verdict and refresh command
… composer as empty (kunchenguid#6163)

* fix(bin): recognize a named Claude session's titled composer rule on cursorless backends

`claude -n <name>` right-aligns the session name in the rule above the
composer. Cursorless backends (Herdr) accepted only solid rules, so the
scan saw the lower rule alone, the idle composer read `unknown`, and
away-mode delivery to a named Claude primary deferred until return.

A rule carrying one right-aligned title now opens a separator pair. It
never closes one, the pair it opens counts only over an agent-glyph row,
and it is never a valid Pi pair, so Pi's verdict is unchanged.

The away-mode composer deferral now names which refusal it was:
unsubmitted text, or no composer shape recognized.

* no-mistakes(review): Reword composer deferral reason to cover busy harness

* no-mistakes(ci): ci-3 (Greptile, user chose to fix): The rule that must always hold is that Claude's titled opening rule is accepted for any non-empty session name that doesn't start with a space and doesn't contain the rule glyph `─`, including a multiword name. The only place that check lives is the title `case` in `_fm_composer_titled_rule_row` (bin/fm-composer-lib.sh). It now reads `case "$title" in ''|' '*|*─*) return 1 ;; esac`, as the user asked, and the comment above the function mentions multiword titles. I added regression fixtures to `test_matrix_claude_named_session_titled_rule` in tests/fm-composer-lib.test.sh, next to the existing single-word `Andrew` cases. They use the title `Andrew Chen` and check that an idle composer on herdr reads `empty` and a composer with typed text reads `pending`. Something you should know: the old pattern already accepted a title with a space in the middle. It only rejected a title ending in a space. So the new multiword fixtures also pass against the old code, and the bot's failure scenario did not actually happen for "Andrew Chen". What the fix really changes is that it drops the trailing-space rejection, as instructed. Checks run: tests/fm-composer-lib.test.sh and tests/fm-composer-ghost.test.sh both pass (exit 0, no failures), and `shellcheck -x` is clean on both changed files

* no-mistakes(review): Match unnamed-ghost fixture rule width to the partner rule
…process groups (kunchenguid#7666)

* fix(tests): reap Pi test process groups and stop arms whose ground is removed

Orphaned stub-arm pollers from tests/fm-pi-watch-extension.test.sh outlived
a failed case: they waited for release/stop files the failed driver never
wrote, teardown only removed the temp root, and the loops had no other exit.

Run each node driver in its own recorded process group and reap those groups
in teardown before the root is removed; end every blocking stub loop once its
own script is gone. bin/fm-watch-arm.sh now exits with a typed ground-removed
line within one poll when its script, state directory, or home is deleted,
instead of polling through the successor window.

* no-mistakes(document): Document arm exit when its ground is removed
…alidating (kunchenguid#5947)

* fix(bin): defer wedge escalation while an attributed validation run is running or fixing

While a no-mistakes run executes a round, the worker is correctly idle behind
its own blocking drive call, yet the watcher's wedge timer escalated a possible
wedge every FM_STALE_ESCALATE_SECS. The timer now consults the attributed run
step before escalating: a run the current-state reader reports as
validating (running) or validating (fixing) for a named run defers onto the
FM_PAUSE_RESURFACE_SECS recheck cadence, aged from a marker bound to that run
id. Parked, terminal, ci-monitoring, coarse, runless, absent and unreadable
runs keep the unchanged escalation ladder.

* test: count only the validating-run read in the unarmed parked-gate probe

* no-mistakes(document): add .validating-* to watcher-internals marker inventory

* no-mistakes(document): Update fm-watch.sh comments for validating-deferral chain

* no-mistakes(document): Document validating-run deferral in guard and handoff docs

* feat(watch): gate the validating-run wedge deferral behind config/wedge-defer-validating

The deferral changed the default for every home, unlike the comparable
config/wedge-defer-parked-gate and config/wedge-defer-worker-wait waits.
Absent the new presence flag the wedge timer spends no current-state read
for it and keeps the unchanged ladder.
…unchenguid#6300)

* fix(session-lock): identify a harness session on Windows/Git Bash

Every session start on Cygwin (Git for Windows) refused the fleet lock and
dropped to read-only with "cannot locate harness process in ancestry", so
spawning, steering, merging, the wake-queue drain, and supervision repair were
skipped on every start. Two independent causes, both on the identity path.

Cygwin's ps has no -o option at all and fails the whole invocation with
"unknown option -- o", so the ancestry walk aborted on its first hop. The walk
now reads comm, args, and ppid through accessors that fall back to Cygwin's
fixed ps columns, leaving the procps/BSD path unchanged.

That alone does not resolve the session: the parent link from a shell the
harness spawns does not cross the Cygwin boundary, and Cygwin reports that
shell's PPID as 1, so no walk can reach a harness that is a native Windows
process. Identity is instead taken from the session pid the harness publishes
and confirmed against the Windows process table before it is used - the pid
must still be live and its executable must independently identify a verified
harness - so an absent, stale, or non-harness value is discarded rather than
bound.

Walking the real Windows parent chain was implemented and then removed as
unsafe. MSYS emulates exec by spawning a fresh Windows process and exiting the
old one, so intermediate shells vanish and a child's recorded parent is
routinely a pid that no longer exists; Windows never reparents an orphan, so
that dangling id stays and can be reissued to an unrelated process. Following
it can bind a home's lock to the wrong process, which is the failure this file
exists to prevent. A harness that publishes nothing stays unresolved, which
leaves the session read-only exactly as before.

Windows pids are tagged rather than stored bare. They are a different namespace:
kill -0 reports a live Windows process as dead, and the number can collide with
an unrelated live Cygwin pid. The tag makes the value non-numeric, so a consumer
that treats it as a local pid - including a future kill - refuses it instead of
acting on the wrong process.

fm-sessionstart-nudge.sh carried a private second copy of the ownership walk and
so stayed wrong after the owner was fixed, nudging a session that already held
the lock. It now asks the owning function.

Verified on Windows 11 (Git Bash, Cygwin ps 3.4.10): the lock is acquired,
reports its holder, is idempotent, and refuses a bogus, dead, or non-harness
published pid. Regressions cover both platform departures behind a fake process
table, so they run on Linux and macOS CI too. The pre-existing e2e failure in
this suite on Windows is unchanged from main; it cannot exec its symlinked
fixture.

Refs kunchenguid#3396

Claude-Session: https://claude.ai/code/session_01F9T29YDqYSkQeDTC2Nfi7h
(cherry picked from commit 8b281b7)

* fix(session-lock): keep the nudge's own ancestry question intact

Delegating the nudge to fm_session_lock_owned_by_self changed the question it
asks. The nudge asks whether a process in this ancestry took the lock; the
ownership predicate additionally requires a verified harness in that ancestry,
so a session whose lock was written by a plain shell started being nudged to
run session start again. tests/fm-sessionstart-nudge.test.sh pins the looser
contract deliberately.

The walk stays local, now reading ppid through the portable accessor so it also
survives a ps with no -o option, and defers to the ownership predicate only for
a Windows-tagged holder, which is not in this process table at all.

(cherry picked from commit 0baf64f)

* fix(session-lock): accept a tagged identity in every gate that reads the lock

The Windows identity added in this branch introduced a second shape into
state/.lock. Six gates read that field, and each one answered "is this value
usable" with its own inline numeric test, so every one of them read a valid
Windows holder as malformed.

The visible cost was concentrated in startup completion: the record was never
written, and the clear/compact check that consumes it could never match, so
every clear or compact on Windows repeated the full startup sequence. The
deferred network sweeps reported ownership as changed when it had not, and the
Stop auto-arm treated a dead Windows session as an unreadable lock rather than
a recoverable one. fm-lease.sh was the outlier: rather than refusing a value it
could not use, `tr -cd '0-9'` reduced the tag to its digits and produced a
number naming an unrelated process in the local table. It happened to fail
closed downstream, but deriving a wrong-namespace pid is precisely what the tag
exists to prevent, so it now takes the value whole and requires a local pid.

fm_session_pid_valid is now the single owner of that question and every gate
delegates to it, so a third identity shape cannot split them again.

Verified against the shipped bytes of each gate with a tagged lock: startup
completion now records and is recognized (main's gate reruns the full startup
on the same input), both network-ownership gates authorize their sweeps, and a
tagged lock yields no lease holder pid instead of 7204.

(cherry picked from commit 9256182)

* fix(session-lock): gate the tagged identity on Windows and read it in every lock reader

Accept win:<digits> only where fm_win_boundary_applies, with an all-digit
remainder, so a Linux/macOS reader of a win:N lock stays inert as before.
The sessionstart nudge routes its tagged case through fm_win_untag_pid.
fm_session_lock_foreign_owner_live and fm_session_lock_inspect now validate
through fm_session_pid_valid and resolve a tagged holder through
fm_harness_pid_alive, so a live win:N holder reads as a live foreign owner
and as held; unverifiable tagged holders stay unknown.

* no-mistakes(document): Document Windows tagged session-lock identity in nudge and scripts docs

* fix(session-lock): keep task leases and stale detection working for a tagged holder

A Windows session can now hold the session lock as win:<pid>, but the task
lease still accepted only a local pid: the claim recorded the short-lived
fm-lease.sh process instead, and fm_lease_live rejected the tagged lock, so
the lease was never live and the other supervision actor could clear it and
act on the same task. The lease now records a live tagged lock holder whole
and treats it as live while it is still the lock holder and a verified
harness in the Windows process table. The session-lock library is loaded
only for a win:-shaped value, so POSIX lease verdicts are unchanged.

fm_session_lock_inspect left an exited tagged holder unknown, because
fm_win_command could not tell an absent pid from an unreadable table. It now
returns 1 only when a readable table proves the pid absent and 2 when the
table cannot be read, so inspection matches the local-pid contract: absent
is stale, a live non-harness is unknown with no live harness, and an
unreadable table stays unknown.

* no-mistakes(review): Restrict caller-supplied lease holder pid to local numeric

* no-mistakes(review): Pass tagged session-lock holder whole to task lease

* no-mistakes(review): Make tagged lease-holder regression tests fail without their fixes

* fix(session-lock): take a Windows CLAUDE_PID only with its session id

On Windows the session identity comes from CLAUDE_PID, which every process
started from a Claude pane inherits, so an unrelated process carrying a copy
could take the lock as that session and both would own it. The bridge now
accepts CLAUDE_PID only together with CLAUDE_CODE_SESSION_ID. The trusted
session-id gate matches the tagged win:<pid> form, so that id is recorded
beside the lock, and the anchor stays tagged. fm-lock.sh and the ownership
readers refuse the same tagged pid under a different recorded id as another
live session's. A non-Claude primary hand-started from a Claude pane inherits
both values and still resolves as that session; that remains a known
limitation. POSIX behavior is unchanged: every new branch applies only to a
tagged pid.

* fix(session-lock): let a Windows /clear re-key its own lock

/clear keeps the Claude process, so CLAUDE_PID stays while the session id
changes. Refusing the same tagged pid under a different recorded id locked a
session out of its own lock after /clear, which POSIX handles by re-keying
the sidecar. Drop that refusal: a Windows CLAUDE_PID is still taken only
together with its CLAUDE_CODE_SESSION_ID, and that id is still recorded
beside the lock and re-keyed on /clear as on POSIX.

* no-mistakes(review): Give tagged host test a session id; clarify Windows lock error

* no-mistakes(document): Point the trust-gate comment to the Windows limitation

---------

Co-authored-by: Lloyd <lloydkakunguwo@gmail.com>
Merge upstream 5d718d5 with a real second parent. Preserve Azure DevOps delivery and standalone report compaction, intent and custody contracts, quiet and away authority, supervision host handback and cleanup guards, auth forwarding, fleet sync, fork skills and CI shards.

Adopt upstream for historical BEHIND-ONLY paths and equivalent snapshot/skill owners. Reconcile overlapping behavior and fixtures; verify lint, source-aware ShellCheck, affected delivery and supervision suites, and live Claude/Pi guards. Ruby-dependent workflow assertions require CI on this host.
…2", failed on one test in tests/fm-supervision-host.test.sh: `test_tagged_lock_holder_reaches_the_engine_whole`, which reported "tagged: the wake was not handled". This PR's merge brought the test in from upstream (kunchenguid#6300). The test is upstream's apart from one extra `ps` stub line added during the merge; the code it exercises (the session-lock library) is identical to upstream. Root cause: the test's wait budget is too short for the fork's CI runners. - Upstream's CI runs this test in about 23s. - The fork's CI runs this suite about 2x slower than upstream's: about 2.1x slower in this run, and about 1.9x at a610a4e, the fork's last green main run. - The sibling away-posture test (`test_away_wake_is_handled_on_the_engine_and_never_reaches_main`) uses the same budget of 150 polls for the watcher plus 250 polls for the wake to be handled. It already takes 44–46s on fork CI, both at a610a4e and in this run. - The tagged test is a bit slower still, because its fake `ps` and `uname` scripts start an extra shell on every lookup. It needs roughly 48s and timed out at about 45s. The supervision-host log showed only the host start line, with no close and no error. - Locally, upstream (5d718d5) and HEAD take the same time per test (for example 4.8s/4.8s and 4.5s/4.3s), so the fork's code does not slow this suite down. Invariant: each asynchronous wait in this suite must give enough time for the fork's CI runner to finish. Among this test's waits only the handled wait ran out; the watcher wait passed. Other fork merges have already widened waits in this suite the same way (450 polls for `watcher_live`, 600 for `hook_exited`). The away sibling has the same 250-poll handled wait and still passes, so I left it unchanged. Change: in the tagged test, the handled wait goes from 250 to 450 polls, with a two-line comment explaining why. The test's logic and assertions are unchanged. Verification: - `shellcheck -x tests/fm-supervision-host.test.sh` is clean. - The tagged and away tests pass in isolation, and the full tests/fm-supervision-host.test.sh suite passes locally (exit 0, 97 passes, no failures). - A CI rerun is needed to confirm the fix on the fork's runners. Not addressed here: "Behavior portable serial 1" (ci-2) was cancelled when it hit the 30-minute job timeout, because tests/fm-watch-triage.test.sh alone ran about 27 minutes. Upstream main's serial 1 has the same cancellation (runs 39940220247 and 39968718606), so it is inherited from upstream and not caused by this PR
@jredgard
jredgard merged commit 3d950e6 into main Oct 23, 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.