Skip to content

Merge upstream firstmate into the fork - #35

Merged
cloud-practitioner merged 11 commits into
mainfrom
fm/fm-merge-upstream-2
Oct 3, 2026
Merged

cloud-practitioner merged 11 commits into
mainfrom
fm/fm-merge-upstream-2

Conversation

@cloud-practitioner

Copy link
Copy Markdown
Owner

Intent

firstmate-maint take 1 and 2, after that, create pr to merge update from upstream.
With PRs 31 and 33 merged, start the merge-from-upstream PR as planned (merge commit only, upstream changes only).

The plan: a fresh PR to cloud-practitioner/firstmate that brings kunchenguid/firstmate main into the fork, following the same rules as the earlier upstream-merge PR 29: it lands as a merge commit only, never squashed or rebased; its net change is exactly the upstream merge (upstream changes only); no pipeline auto-fix content is added; title "Merge upstream firstmate into the fork". If upstream has nothing new, report that instead of opening an empty PR.

What Changed

  • Merge upstream main through d719ef3d, preserving hold reasons, validating completion inventory origins, and repairing watcher takeover and supervision hand-back recovery.
  • Correct Claude composer and slash-command detection and seeded Pi secondmate trust handling; reduce remote polling process churn, recognize clone roots by filesystem identity, and clarify accepted contribution verdict actors.
  • Retry memory-exhausted ShellCheck roots without source following within their original bounds, report fallback coverage, and update regression tests and operational documentation.

Risk Assessment

🚨 High: Merge provenance is consistent with the upstream-only intent, but an interrupted hold reassociation can incorrectly pass completion and cleanup gates, requiring explicit human approval.

Testing

Git merge checks, focused regressions, and isolated live CLI, watcher, remote-worker, Pi, and Claude scenarios passed after correcting fixture setup; terminal images and product transcripts were captured, native supervision hand-back remained blocked by the gate-root scope guard, lint stayed deferred to its owning phase, and all worktree scratch was removed without changing the submitted merge.

  • Live validation: ✅ go - 11 of 13 scenarios driven live against the product
Scenario Result Live Evidence
Merge upstream while retaining fork fixes and adding no pipeline content ✅ pass live round2-merge-proof.log; final Git history and clean-worktree checks
Hold a multiline Unicode reason and read it unchanged through backlog and fleet views ✅ pass live hold-cli.log; hold-snapshot.json
Reject wrong-origin and self completion inventories while accepting a durable separate captain call ✅ pass live hold-cli.log
Sync a symlink-aliased clone without treating a non-clone directory as the enclosing repository ✅ pass live fleet-sync-cli.log
Poll an append-only log without returning incomplete lines or output on an unchanged timeout ✅ pass live remote-delta-live.json
Detect same-size same-second rewrites and truncation, and reject log path traversal ✅ pass live remote-delta-live.json
Preempt a home's long poll for a short command without interrupting another home's poll ✅ pass live remote-worker.log; remote-job-delta.txt
Record supported contribution verdict actors and refuse an unknown actor without mutation ✅ pass live contribution-actors.log
Take ownership of a watcher cycle while preserving acknowledgement and surfacing subsequent queued work ✅ pass live watcher-live.log; watcher-ownership.tsv
Start a seeded Pi secondmate past project trust without permanently trusting other project paths ✅ pass live pi-live-spawn.log; pi-seeded-stall.png; pi-seeded-approved.png; pi-unseeded-stall.png
Recognize a titled Claude composer, protect its existing slash draft, and submit grey slash commands ✅ pass live claude-composer.log; claude-slash.png; claude-slash-delivered.txt; claude-after-exit-process.json
Complete native Claude supervision hand-back and repark after a successor closes ⏸️ untested no The prescribed private-tmux primary used the normal login, acquired its isolated lock, and fired the native Stop callback, but the primary-scope predicate rejects the required linked gate root before…
Retry a memory-limited ShellCheck root without external-source expansion ⏸️ untested no This assigned Test phase explicitly forbids running linters and static analysis. Exercising the real retry requires authority held by the separately owned Lint/CI phases; substituting a fake linter wo…
Evidence: Validation results, setup retries, limitations, and reproduction notes

Source: Validation results, setup retries, limitations, and reproduction notes

# Targeted live validation — upstream merge

Submitted and final feature-branch head: `109825e2d232b179d8880c2cb123f3f1176a0b2f`.

## Product evidence from this turn

- `hold-cli.log` and `hold-snapshot.json`: real tasks-axi plus Firstmate CLI preserve multiline, parentheses, Unicode, and literal encoding-like hold reasons; wrong-origin and self-inventories refuse without changing the origin; a valid inventory verifies before and after the captain answer.
- `fleet-sync-cli.log`: real Git remote/clone fixtures update a symlink-aliased clone to v2 and skip a non-clone without changing the enclosing gate checkout.
- `remote-delta-live.json`: real delta reader returns exit 75 silently on no change, waits for complete lines, detects same-inode/same-size/same-second prefix rewrites and truncation, and rejects path traversal.
- `remote-worker.log`: real job worker preempts only the same home's long poll (exit 76), completes its short job, and leaves the other home's poll running until its own reply arrives.
- `contribution-actors.log`: real verdict CLI persists captain/fleet/maintainer/nobody actors and rejects an unknown actor without mutating the record.
- `watcher-live.log` and `watcher-ownership.tsv`: real watcher arm takeover replaces exactly the owned watcher, preserves its acknowledged downtime generation, then surfaces and acknowledges new queued work.
- `pi-live-spawn.log`: real home seeder and spawner run the unchanged submitted product materialized inside a disposable source copy; the secondmate home is outside that copy's root but inside the gate worktree. Real Pi 1.0 first prompts without approval, then product spawn passes project trust with actual tracked extensions and leaves disposable trust.json unchanged. This is intentionally offline startup validation, not a model-response or login test. `pi-seeded-stall.png`, `pi-seeded-approved.png`, and `pi-unseeded-stall.png` show the terminal surfaces.
- `claude-composer.log`: real Claude 2.1.288 under a named non-default Herdr lab has an empty titled composer; typed grey `/exit` remains readable and is protected against injection over its draft. The real adapter clears the draft, delivers a rename, then exits to the shell, confirmed by real process-info. Herdr prepare/provision/run/viewer/teardown used only the lab helper, and teardown verified its unchanged default-session tripwire. `claude-titled.png` and `claude-slash.png` are rendered from the actual captured ANSI viewport, not mock UI.
- `round2-merge-proof.log`: real Git merge-tree reproduces the merge. Every non-conflicted path matches exactly; the sole test-call-list conflict retains the fork cleanup wrapper and all upstream/fork cases. There are ten upstream commits and exactly one new first-parent merge commit.

## Focused regression execution and setup retries

`round2-supervision-regressions.log` records the seven changed hand-back/takeover/failure-notice cases. `round2-targeted-regressions.log` records the two changed watch-arm cases and the initial Pi fixture refusal. The Pi fixture was initially inside the product root and also inherited the operator's Herdr backend selection. Its local unchanged-product copy and explicit tmux backend corrected that test setup; `round2-pi-spawn-regressions.log` records the successful Pi/pi-signed approval, ordinary-worker omission, and unsupported-flag cases. These stub-assisted regression tests are supporting evidence, not live harness proofs.

A first Pi live attempt closed its last baseline tmux session immediately before making another, racing server shutdown. Keeping a second session alive fixed the disposable setup. A later clean-shell re-drive passed; the latest Pi log and images are from that successful run.

## Limits and phase boundaries

The real Claude primary acquired the isolated session lock and fired its native Stop callback, but the shipped primary-scope predicate rejects linked gate worktrees. `primary-supervision.log` and `primary-final.png` show the attempted check. Full native supervision hand-back is **untested**: this turn did not forge a primary identity, relocate the required primary launch root, or bypass the guard. The real watcher takeover and focused host/hook regressions cover the changed lower-level behavior. A separately authorized normal-primary lab run or a trusted runbook/product accommodation for gate-root primary validation is needed for the full native loop.

The ShellCheck memory-ceiling retry is **untested in this phase**, whose instructions expressly prohibit linters/static analysis. It belongs to the separately owned Lint/CI phases. PR creation, exact PR title, publication, and merge-method enforcement also remain with the outer executor; this turn did not perform them.

No complete repository suite, linter, formatter, pipeline-control command, push, PR, or CI phase ran. All intentionally created fixtures stayed inside this worktree; normal harness/toolchain ephemeral files were incidental. Both rounds' scratch selectors, downloaded local tools, copied product trees, caches, private sockets, and disposable labs were removed. Final `git status --porcelain` is empty, the feature head is unchanged, and no product-source fix or pipeline-fix commit was added. The previously declined interrupted-reassociation concern was not changed or re-reported.

Reproduction drivers are retained under `drivers/`; their `.test-phase-tmp` dependencies were disposable and removed, so rebuild local tool/product fixtures before reusing them.
Evidence: Upstream-only merge proof

Source: Upstream-only merge proof

Git merge proof for submitted commit 109825e2d232b179d8880c2cb123f3f1176a0b2f
parents: e42087e4513c4483d832aa8898b28efd410fdac8 d719ef3d9abdd11b0a8aea57c74de074feca99b9
first-parent commits after base: 1 (the merge only)
new upstream commits: 10
merge-tree: only tests/fm-supervision-host.test.sh conflicted
all non-conflicted paths byte-identical to Git automatic merge
sole conflict resolution preserves fork cleanup wrapper and union of upstream/fork executable cases without duplicates
tracked worktree changes: none
No source fixes or feature-branch commits were added by Test. PR creation/title/merge method belong to the outer executor, not this test phase.
Evidence: Hold reasons and completion guards

Source: Hold reasons and completion guards

Real tasks-axi and Firstmate CLI, isolated marked lab; no stubs
ok: added review-a (scout, repo demo) -> In flight
task:
  id: review-a
  title: Investigation review-a
  state: in_flight
  blocked: no
  blocked_by: none
  held: no
  hold_reason: "-"
  hold_kind: "-"
  hold_until: "-"
  kind: scout
  repo: demo
  priority: "-"
  created: 2026-10-03
  closed: "-"
  deps: none
  links: none
  body: ""
help[2]:
  - Run `tasks-axi done review-a --pr <url>` when it ships
  - Run `tasks-axi block review-a --by <other>` to record a dependency
ok: added review-b (scout, repo demo) -> In flight
task:
  id: review-b
  title: Investigation review-b
  state: in_flight
  blocked: no
  blocked_by: none
  held: no
  hold_reason: "-"
  hold_kind: "-"
  hold_until: "-"
  kind: scout
  repo: demo
  priority: "-"
  created: 2026-10-03
  closed: "-"
  deps: none
  links: none
  body: ""
help[2]:
  - Run `tasks-axi done review-b --pr <url>` when it ships
  - Run `tasks-axi block review-b --by <other>` to record a dependency
call-a
task:
  id: call-a
  title: Choose safe mode
  state: queued
  blocked: no
  blocked_by: none
  held: yes
  hold_reason: "Choose (safe mode)\nKeep café and literal fm-hold-v1:YWJj."
  hold_kind: captain
  hold_until: "-"
  kind: captain
  repo: demo
  priority: "-"
  created: 2026-10-03
  closed: "-"
  deps: none
  links: none
  body: "Captain hold set: 2026-10-03T05:01:22Z\nCaptain hold origin: review-a\n\nOrigin: review-a"
{
  "id": "call-a",
  "hold_kind": "captain",
  "hold_reason": "Choose (safe mode)\nKeep café and literal fm-hold-v1:YWJj.",
  "captain_actionable": true
}
fm-captain-hold: captain-held task call-a was held for origin review-a, not review-b; hold a task for review-b or list the right one
Wrong-origin completion refused
review-a
fm-captain-hold: origin review-a cannot be its own captain-call inventory entry; hold a separate captain task for the call and list that task
Self-inventory refused
complete: review-a captain-call inventory reviewed (call-a)
verified: review-a captain-call inventory
Recorded inventory:
decisions_reviewed=1
decision_keys=call-a
answered: call-a
verified: review-a captain-call inventory
task:
  id: call-a
  title: Choose safe mode
  state: done
  blocked: no
  blocked_by: none
  held: no
  hold_reason: "Choose (safe mode)\nKeep café and literal fm-hold-v1:YWJj."
  hold_kind: captain
  hold_until: "-"
  kind: captain
  repo: demo
  priority: "-"
  created: "-"
  closed: 2026-10-03
  deps: none
  links: none
  body: "Resolution recorded by fm-captain-hold.\nDecision digest: 514c9836f8d253f23711cefce39156bde1bac6831ba2a410e27261254f38c9bc\nResolution mode: answered\n\nCaptain decision:\nUse safe mode.\n\nCaptain hold origin: review-a\n\nOrigin: review-a"
Evidence: Aliased clone sync and non-clone refusal boundary

Source: Aliased clone sync and non-clone refusal boundary

Local bare remote plus real fleet-sync, symlink alias and non-clone boundary
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
●  WATCHER DOWN - SUPERVISION IS OFF
●  2 task(s) in flight, but no watcher has a fresh beacon (last beat: never, grace 300s).
●  Trust the emitted supervision protocol for this harness; do not use shell & for watcher repair.
●  This is a supervision warning only; the guarded operation WILL still run.
●  repair a missing or failed watcher cycle with the Pi tool fm_watch_arm_pi, or restart Pi with -e ~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C/.pi/extensions/fm-primary-turnend-guard.ts -e ~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C/.pi/extensions/fm-primary-pi-watch.ts if the extensions are not loaded.
●━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
alias: synced cb41cf0..811de58
Synced clone value: v2
WARNING: watcher still down (same stale episode; last beat: never, grace 300s) - full banner already printed this episode.
not-a-clone: skipped: not a clone root (git would act on ~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C)
Enclosing worktree HEAD remained 109825e2d232b179d8880c2cb123f3f1176a0b2f
Evidence: Live delta-reader protocol responses

Source: Live delta-reader protocol responses

[
  {
    "scenario": "unchanged log closes with no data",
    "exit": 75,
    "response": "",
    "stderr": ""
  },
  {
    "scenario": "append waits for newline then returns whole line",
    "exit": 0,
    "response": "schema=fm-remote-delta.v1\nstatus=delta\npath=state/replies.status\nfrom_offset=9\nto_offset=27\nfrom_prefix_sha256=25718360e05d3c2d0963d1381e9dd4dae5fca789244ee4b9f861adcc0cc96218\nto_prefix_sha256=22016a7494b2e6d8733d91d2ffdaf1b2e61080f92c455e7f095db23ac93d55b6\npayload_sha256=0494494627db006dbfb1ccf6310f6de1de6c1ece9cd4b74127176022036177a6\npayload_bytes=18\nreason=\n\npartial-completed\n",
    "stderr": ""
  },
  {
    "scenario": "same-size same-second in-place rewrite breaks continuity",
    "exit": 0,
    "response": "schema=fm-remote-delta.v1\nstatus=continuity-broken\npath=state/replies.status\nfrom_offset=11\nto_offset=11\nfrom_prefix_sha256=e49c81e2d2f84e259d40e2fb8192f3bcd198b355184845d76d8f58807d0d78ee\nto_prefix_sha256=105574d777006ec9bddf92f16dae3a0b7aaec1a3161e63033aa041014ec15223\npayload_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\npayload_bytes=0\nreason=prefix-changed\n\n",
    "stderr": "",
    "filesystem": {
      "same_inode": true,
      "same_size": true,
      "same_epoch_second": true,
      "mtime_before_ns": 1791003684044317818,
      "mtime_after_ns": 1791003684234971719
    }
  },
  {
    "scenario": "truncation breaks continuity rather than rebasing cursor",
    "exit": 0,
    "response": "schema=fm-remote-delta.v1\nstatus=continuity-broken\npath=state/replies.status\nfrom_offset=11\nto_offset=2\nfrom_prefix_sha256=e49c81e2d2f84e259d40e2fb8192f3bcd198b355184845d76d8f58807d0d78ee\nto_prefix_sha256=73cb3858a687a8494ca3323053016282f3dad39d42cf62ca4e79dda2aac7d9ac\npayload_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855\npayload_bytes=0\nreason=truncated\n\n",
    "stderr": ""
  },
  {
    "scenario": "reject relative path traversal",
    "exit": 1,
    "response": "",
    "stderr": "error: log traversal is not allowed: ../outside.status\n"
  }
]
Evidence: Remote worker preemption and home isolation

Source: Remote worker preemption and home isolation

Real worker and real tracked commands, private account/home/job queue; no SSH or command stubs
job-uJ2BTb
job-YLJHZV
Both independent home lanes: own=running other=running
job-8UTpC7
Same-home long poll preemption exit=76
Other-home long poll state after preemption=running
Short command output:
⁣FIRSTMATE_OP: v1 launch-brief: Short interactive job payload
Other-home completed result:
schema=fm-remote-delta.v1
status=delta
path=state/replies.status
from_offset=0
to_offset=21
from_prefix_sha256=e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
to_prefix_sha256=f48c765dd13da447c3fbbfcc06e766799ecef59e14a33b3c0c53fd6f4f8e4003
payload_sha256=f48c765dd13da447c3fbbfcc06e766799ecef59e14a33b3c0c53fd6f4f8e4003
payload_bytes=21
reason=

Remote reply arrived
No pending consumer records: 0
Evidence: Contribution verdict actor behavior

Source: Contribution verdict actor behavior

Real verdict command against an isolated owned contribution record; no network or forge mutations
delivery
{
  "head": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "source": "https://github.com/example/disposable-lab/pull/1#issuecomment-1",
  "actor": "captain",
  "summary": "Lab judgment"
}
{
  "head": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "source": "https://github.com/example/disposable-lab/pull/1#issuecomment-1",
  "actor": "fleet",
  "summary": "Lab judgment"
}
{
  "head": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "source": "https://github.com/example/disposable-lab/pull/1#issuecomment-1",
  "actor": "maintainer",
  "summary": "Lab judgment"
}
{
  "head": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa",
  "source": "https://github.com/example/disposable-lab/pull/1#issuecomment-1",
  "actor": "nobody",
  "summary": "Lab judgment"
}
fm-contributions: invalid required actor 'bogus'; expected one of: captain, fleet, maintainer, nobody
Invalid actor left persisted contribution record unchanged.
Evidence: Live watcher takeover and subsequent wake

Source: Live watcher takeover and subsequent wake

Real arm/watcher/wake queue and drain in a marked home; no stubs or native-primary impersonation.
WAKE_ACK_REQUIRED: after handling completes run bin/fm-wake-drain.sh --ack-through 0 --recovery-generation 3680811.1791003681.avtK7W
Initial owner arm=3682213 watcher=3682269
Take-over arm=3683336 watcher=3685879
Handled downtime generation retained exactly: acked:handling:3680811.1791003681.avtK7W
New owning cycle emitted:
watcher: started pid=3685879 (beacon fresh)
check: rearm-resurface
1791003689	1	check	lab-live	check: lab-live - new actionable test record
WAKE_ACK_REQUIRED: after handling completes run bin/fm-wake-drain.sh --ack-through 1 --recovery-generation 3680811.1791003689.BVV67s
Ownership ledger:
arm_pid=3682213	watcher_pid=3682269	origin=started	started_at=1791003683	ended_at=1791003684	exit_code=143	signal=TERM	reason=signal-exit	beacon_age=1	lock_before=pid:3682269|identity:linux-starttime=10305974 cmdline-hex=62617368002f686f6d652f6e6f64652f2e6e6f2d6d697374616b65732f776f726b74726565732f3435303431316233653637632f30314d335a5a5a445239583248354e38504a37414257485a33432f62696e2f666d2d77617463682e736800	lock_after=pid:none|identity:none	successor=none
arm_pid=3683336	watcher_pid=3682269	origin=attached	started_at=1791003684	ended_at=1791003685	exit_code=unknown	signal=unknown	reason=taken-over	beacon_age=2	lock_before=pid:3682269|identity:linux-starttime=10305974 cmdline-hex=62617368002f686f6d652f6e6f64652f2e6e6f2d6d697374616b65732f776f726b74726565732f3435303431316233653637632f30314d335a5a5a445239583248354e38504a37414257485a33432f62696e2f666d2d77617463682e736800	lock_after=pid:none|identity:none	successor=started:3685879
arm_pid=3683336	watcher_pid=3685879	origin=started	started_at=1791003685	ended_at=1791003691	exit_code=0	signal=none	reason=actionable-check	beacon_age=1	lock_before=pid:3685879|identity:linux-starttime=10306229 cmdline-hex=62617368002f686f6d652f6e6f64652f2e6e6f2d6d697374616b65732f776f726b74726565732f3435303431316233653637632f30314d335a5a5a445239583248354e38504a37414257485a33432f62696e2f666d2d77617463682e736800	lock_after=pid:none|identity:none	successor=none
Evidence: Real seeded Pi launch

Source: Real seeded Pi launch

Real fm-home-seed and fm-spawn, submitted product copy, real Pi, private 120x40 tmux; offline disposable agent store.
scaffolded: ~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C/.prPf/data/lab-pi/brief.md (secondmate charter)
home=~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C/.qrQZ
Baseline actually seeded home: Trust project folder dialog observed.
fm-gate-refuse: gate agent lifecycle permitted only against lab home ~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C/.prPf
warning: secondmate lab-pi sync skipped before launch: primary default-branch commit cannot be resolved
spawned lab-pi harness=pi kind=secondmate mode=secondmate yolo=off window=firstmate:fm-lab-pi worktree=~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C/.qrQZ
Product-spawned Pi passed trust and reached the TUI; trust.json remained {}.
Persisted task endpoint:
window=firstmate:fm-lab-pi
worktree=~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C/.qrQZ
harness=pi
kind=secondmate
home=~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C/.qrQZ
Visible Pi output:

 ▀▀█  v1.0.0
 █▀ █ escape interrupt · ctrl+c/ctrl+d clear/exit · / commands · ! bash · ctrl+o more
 Press ctrl+o to show full startup help and loaded resources.

 Pi can explain its own features and look up its docs. Ask it how to use or extend Pi.


[Context]
  ~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C/AGENTS.md, AGENTS.md

[Skills]
  afk, agent-skill-trigger-index, ahoy, ask-user-authority, away-quiet-supervision, bearings, bootstrap-diagnostics,
captain-hold-lifecycle, decision-hold-lifecycle, diagnostic-reasoning, firstmate-codexapp, firstmate-coding-guidelines,
firstmate-orca, fmx-respond, harness-adapters, operational-home-layout, process-event-sources, project-management,
quiet, quota-array-dispatch, scout-completion, secondmate-provisioning, session-start-recovery, ship-landing, stow,
stuck-crewmate-recovery, updatefirstmate, validation-supervision

[Extensions]
  fm-branch-supervision.ts, fm-calm.ts, fm-primary-pi-watch.ts, fm-primary-turnend-guard.ts


 Warning: No models available. Use /login to log into a provider via OAuth or API key. See:
   /workspaces/wp/.pi/install/releases/1.0.0/node_modules/@earendil-works/pi-coding-agent/docs/providers.md
   /workspaces/wp/.pi/install/releases/1.0.0/node_modules/@earendil-works/pi-coding-agent/docs/models.md

 Error: No API key found for the selected model.

 Use /login to log into a provider via OAuth or API key. See:
   /workspaces/wp/.pi/install/releases/1.0.0/node_modules/@earendil-works/pi-coding-agent/docs/providers.md
   /workspaces/wp/.pi/install/releases/1.0.0/node_modules/@earendil-works/pi-coding-agent/docs/models.md

 Warning: tmux extended-keys is off. Modified Enter keys may not work. Add `set -g extended-keys on` to ~/.tmux.conf
 and restart tmux.

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────
~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C/.qrQZ (main)
0.0%/0 (auto)                                                                                                    unknown
![Seeded Pi baseline trust dialog](https://github.com/user-attachments/assets/c8776296-18bf-4ed5-bef8-f64b4235a32b) - Evidence: [Seeded Pi baseline trust dialog](https://github.com/cloud-practitioner/firstmate/blob/6b6f888e517180e321a39c0b31a9a633b8bb222a/.no-mistakes/evidence/fm/fm-merge-upstream-2/pi-seeded-stall.png) ![Product-spawned Pi past trust with actual extensions; intentionally offline](https://github.com/user-attachments/assets/a10159a4-e386-45b9-bd54-1b2e25582550) - Evidence: [Product-spawned Pi past trust with actual extensions; intentionally offline](https://github.com/cloud-practitioner/firstmate/blob/6b6f888e517180e321a39c0b31a9a633b8bb222a/.no-mistakes/evidence/fm/fm-merge-upstream-2/pi-seeded-approved.png) ![Unseeded Pi still requires project trust](https://github.com/user-attachments/assets/99fcfb4f-2c4e-42c0-97c4-4cd57780166b) - Evidence: [Unseeded Pi still requires project trust](https://github.com/cloud-practitioner/firstmate/blob/6b6f888e517180e321a39c0b31a9a633b8bb222a/.no-mistakes/evidence/fm/fm-merge-upstream-2/pi-unseeded-stall.png)
Evidence: Real Claude composer classification and delivery

Source: Real Claude composer classification and delivery

Named lab=fm-lab-round2 pane=w1:p1; normal Claude login, isolated FM_HOME; no model prompts
viewer attached to fm-lab-round2 (pid 3708352)
Named-session idle composer state=empty
Visible typed slash-command payload=/exit
Attempt to inject over an existing draft=send-failed
Submit a grey slash command from empty=empty
Exit submit verdict=unknown (a closed agent may read unknown)
Grey slash delivered and /exit returned to the pane shell (real process-info confirms zsh).
![Claude titled composer and visible slash draft](https://github.com/user-attachments/assets/9710f398-91da-44e6-a4cf-4bf8e9f87abc) - Evidence: [Claude titled composer and visible slash draft](https://github.com/cloud-practitioner/firstmate/blob/6b6f888e517180e321a39c0b31a9a633b8bb222a/.no-mistakes/evidence/fm/fm-merge-upstream-2/claude-slash.png)
Evidence: Native primary supervision attempt and scope blocker

Source: Native primary supervision attempt and scope blocker

Real Claude primary in a marked private tmux lab; real Stop hook, host, watcher, status log, drain and acknowledgements.
Native Stop callback entered
Native primary acquired its session lock, and the actual Stop callback fired. The shipped primary-scope predicate refuses this linked gate worktree, so no host can park under the required gate-root launch recipe. Native host hand-back remains untested; no root identity was forged and no guard was bypassed.
- Outcome: 🔧 2 issues found → no changes applied ✅ across 2 runs (40m28s)

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

⚠️ **Review** - 1 error
  • 🚨 bin/fm-captain-hold.sh:986 - An interrupted reassociation can still certify the wrong investigation. Start with task X released from origin A and carrying A's recorded answer, then run hold X --origin B. Line 986 persists B before the backend hold succeeds. Termination before that hold, or failure of the rollback, leaves X associated with B but carrying only A's resolution. complete B X accepts the old resolution and new association, records completion, and permits B's scout cleanup despite no successful B hold. The shared verifier also permits concurrent completion during this window. Related sites: bin/fm-captain-hold.sh:989 (dated hold), :992 (undated hold), :999 (fallible rollback), :882 and :885–893 (durability and origin checks), :1765 (complete), and :1832 (verify). Reject incomplete reassociations at the shared verification boundary, including after interruption. A crash-safe remedy needs durable transition evidence; that remedy—not the defect—requires authorization because it adds state and this PR must remain upstream-only with no pipeline auto-fix content. Explicitly accept the residual risk or authorize an upstream/follow-up correction.
🔧 **Test** - 2 issues found → no changes applied ✅
  • ⚠️ The Test agent did not finish within its invocation budget. Reported: agent run tests timed out after 30m0s: agent last produced output 2s ago (277 observed); agent reported: pi exited: exit status 143. This is a budget or provider-slowness cut, not a code failure. Re-running the same request costs another full budget, so no further attempt is made automatically. If this repository's targeted tests or evidence gathering routinely approach the default 30m0s, raise test_agent_timeout in global config. Respond with fix to spend another budget: a repair turn runs only for selected findings other than this budget cut, then validation re-runs. Or abort and retry after raising the budget.
  • 🚨 Approval is refused: the run worktree at ~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C holds work no Test turn validated, and the steps after Test would commit and publish it. It holds uncommitted changes to .h/, .test-phase-tmp/, tests/.test-phase-fm-spawn-dispatch-profile.test.sh, tests/.test-phase-fm-supervision-host.test.sh, tests/.test-phase-fm-watch-arm.test.sh (inspect with git -C ~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C status and git -C ~/.no-mistakes/worktrees/450411b3e67c/01M3ZZZDR9X2H5N8PJ7ABWHZ3C diff). Respond with fix to validate it, or abort.

🔧 No changes applied.
✅ Re-checked - no issues remain.

  • Live validation: ✅ go - 11 of 13 scenarios driven live against the product
Scenario Result Live Evidence
Merge upstream while retaining fork fixes and adding no pipeline content ✅ pass live round2-merge-proof.log; final Git history and clean-worktree checks
Hold a multiline Unicode reason and read it unchanged through backlog and fleet views ✅ pass live hold-cli.log; hold-snapshot.json
Reject wrong-origin and self completion inventories while accepting a durable separate captain call ✅ pass live hold-cli.log
Sync a symlink-aliased clone without treating a non-clone directory as the enclosing repository ✅ pass live fleet-sync-cli.log
Poll an append-only log without returning incomplete lines or output on an unchanged timeout ✅ pass live remote-delta-live.json
Detect same-size same-second rewrites and truncation, and reject log path traversal ✅ pass live remote-delta-live.json
Preempt a home's long poll for a short command without interrupting another home's poll ✅ pass live remote-worker.log; remote-job-delta.txt
Record supported contribution verdict actors and refuse an unknown actor without mutation ✅ pass live contribution-actors.log
Take ownership of a watcher cycle while preserving acknowledgement and surfacing subsequent queued work ✅ pass live watcher-live.log; watcher-ownership.tsv
Start a seeded Pi secondmate past project trust without permanently trusting other project paths ✅ pass live pi-live-spawn.log; pi-seeded-stall.png; pi-seeded-approved.png; pi-unseeded-stall.png
Recognize a titled Claude composer, protect its existing slash draft, and submit grey slash commands ✅ pass live claude-composer.log; claude-slash.png; claude-slash-delivered.txt; claude-after-exit-process.json
Complete native Claude supervision hand-back and repark after a successor closes ⏸️ untested no The prescribed private-tmux primary used the normal login, acquired its isolated lock, and fired the native Stop callback, but the primary-scope predicate rejects the required linked gate root before…
Retry a memory-limited ShellCheck root without external-source expansion ⏸️ untested no This assigned Test phase explicitly forbids running linters and static analysis. Exercising the real retry requires authority held by the separately owned Lint/CI phases; substituting a fake linter wo…
  • git merge-tree --write-tree HEAD^1 HEAD^2 with parent, history, tree-equivalence, and sole conflict-resolution checks.
  • bash .test-phase-tmp/live-cli.sh — real hold/completion CLI, fleet snapshot, aliased-clone sync, and non-clone boundary.
  • python3 .test-phase-tmp/live-delta.py — complete-line delivery, silent timeout, same-second rewrite, truncation, and traversal rejection.
  • bash .test-phase-tmp/live-remote.sh — real worker, same-home poll preemption, short-job completion, and independent-home isolation.
  • bash .test-phase-tmp/live-contributions.sh — accepted verdict actors and mutation-free invalid-actor refusal.
  • bash .test-phase-tmp/live-watcher.sh — real watcher takeover, preserved acknowledgement, queued wake, drain, and acknowledgement.
  • bash .test-phase-tmp/live-herdr.sh — guarded named Herdr lab, real Claude titled composer, draft protection, slash submission, exit confirmation, and teardown.
  • bash .test-phase-tmp/live-pi-spawn.sh — real seeder/spawner and Pi startup with actual extensions, disposable trust store, and private 120x40 terminal.
  • python3 .test-phase-tmp/live-pi.py — unseeded Pi trust-dialog negative control on a sized and drained pty.
  • bash .test-phase-tmp/live-primary.sh — attempted native Claude Stop-hook supervision from the required gate-root lab.
  • bash tests/.test-phase-fm-supervision-host.test.sh — seven selected upstream hand-back, failure-notice, takeover, interruption, and successor-record cases.
  • bash tests/.test-phase-fm-watch-arm.test.sh — two selected takeover cases.
  • FM_BACKEND=tmux bash .test-phase-tmp/pi-product/tests/.test-phase-fm-spawn-dispatch-profile.test.sh — selected approval, ordinary-worker omission, and unsupported-flag cases after correcting fixture placement and inherited backend selection.
  • node .test-phase-tmp/render-terminals.cjs and convert — rendered actual terminal captures to reviewer-visible HTML, SVG, and PNG; inspected Claude and Pi images.
  • Removed both rounds' disposable fixtures, tools, caches, private sockets, copied product trees, and scratch selectors; verified empty git status --porcelain and unchanged HEAD.
⚠️ **Document** - 2 issues (1 warning, 1 info)
  • ⚠️ docs/supervision-host.md:230 - The new failure-direction paragraph contains two inaccurate claims. exit_to_main republishes the current recovery marker, not necessarily the recorded successor generation; publication after acknowledgement can mint a fresh generation. The Claude fallback also handles announced:handling, not only pending:handling, contradicting “every other refused rewake stays silent.” The announced case is explicitly covered by tests/fm-claude-stop-autoarm.test.sh. Reference watcher-continuity.md#generation-reuse for generation rules and describe both handling states. Left unchanged because pipeline-generated corrections would violate the required upstream-only merge; resolve through an upstream or separately authorized follow-up.
  • ℹ️ tests/fm-remote-delta-read.test.sh:55 - The new test comment says an unchanged log “never runs the snapshot path,” but the implementation and subsequent assertion require one initial snapshot before skipping unchanged samples. Correct this comment in an upstream follow-up; no pipeline fix content was added.
✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

mremond and others added 11 commits October 1, 2026 07:24
…6307)

* fix(bin): name the accepted verdict actors in fm-contributions help and refusal

* fix(ci): Updated tests/fm-contributions.test.sh to assert exactly captain, fleet, maintainer, and nobody in command-emitted help and refusal output. Three focused regressions passed; all three extra-actor mutations were rejected. ShellCheck, syntax, and diff checks passed. Production code remains unchanged
…chenguid#6306)

* fix(bin): recognise a clone root git names with different path spelling

fm-fleet-sync compared git's --show-toplevel with pwd -P as strings, so a clone
root that git recorded with different casing (case-insensitive volume) was
skipped as not a clone root and never refreshed. Compare filesystem identity
instead, which also covers symlink spelling.

* fix(document): Remove stale clone-root comparison comment
)

* test(calm): pin Pi's regular TUI mode where pane assertions read scrollback

Pi 1.0.0 defaults its TUI to a fullscreen alternate-screen mode whose
scrollable transcript is application-owned, so rows that leave the viewport
never enter terminal scrollback and tmux capture-pane -S can no longer see
them. The Pi Calm e2e launches now pass --tui-mode regular wherever the flag
exists so the transcript assertions keep reading real scrollback on both the
Pi 1.0.0 line and earlier Pi lines, which have no such flag and render
regular-only anyway.

* no-mistakes(document): Correct Pi TUI documentation and scrollback rationale
…ries (kunchenguid#6331)

* fix(bin): encode captain-hold reasons and reject self-inventory in complete

hold now stores a reason with parentheses, line breaks, or percent signs
through a reversible percent encoding that every reader decodes, instead of
refusing it. hold --origin records the origin on the held task, and complete
refuses the origin as its own inventory entry and an entry held for a
different origin; holds with no recorded origin are accepted and flagged.

* fix(review): Decode marked hold reasons consistently across readers

* fix(review): Remove unnecessary lifecycle test dispatch

* fix(review): Correct hold origin identity and inventory recovery

* fix(review): Record origins before placing backend holds

* fix(document): Clarify captain-hold validation and reason reader documentation

* fix(ci): Fixed both findings: failed backend holds restore the previous origin, and invalid base64/UTF-8 reasons remain verbatim. Added regressions and documented valid-literal ambiguity. Both failures were reproduced before fixes. Verification: 54 lifecycle tests and 9 wrapper tests passed; 7 Beads-specific cases skipped because tasks-axi is markdown-only. Focused lint and diff checks passed. No pipeline or publication actions performed
* fix(bin): take over the watcher cycle a main-only pass-through leaves

An attended main-only pass-through leaves a successor watcher cycle
running through main's handling turn. The session's next park attached
to that cycle instead of owning it, so the successor's arm, orphaned by
its host's exit, kept owning the watcher while the new park's arm polled
it twice a second until the next close or the park boundary, hours later
in a quiet second mate. A second-mate restart hit this every time, since
its persist request is a main-only close.

The host now records the successor it leaves for main, and the next
host's first cycle runs bin/fm-watch-arm.sh --take-over on it: when that
arm still owns the healthy watcher, the new arm stops it, reports a
reason the cycle delivered first, and otherwise owns a fresh cycle. The
stop's own downtime publication is undone over an acknowledged episode
when no wake was appended in between, so the handover wakes nobody.

* no-mistakes(review): Keep left-arm record until the orphaned arm is gone

* no-mistakes(review): Relinquish successor arm only after durably recording it

* no-mistakes(review): Relinquish successor only after its record reads back

* no-mistakes(document): Clarify successor takeover guarantees and authoritative documentation

* no-mistakes(ci): Fixed ci-1: acknowledgement restore now requires the exact taken-over arm/watcher ledger row with signal=TERM, awaited within a short bound. Otherwise takeover proceeds without erasing downtime. Added a self-exit regression confirmed failing before the fix and passing afterward; ordinary takeover tests and the full watcher-arm suite pass. Updated Generation reuse documentation. Syntax, diff checks, and ShellCheck pass with existing SC1091/SC2034 warnings excluded. ci-2 remains unchanged

* no-mistakes(document): Clarify watcher take-over recovery and restart limits
…cessor already closed (kunchenguid#6355)

* fix(bin): restore supervision host hand-back continuity

* no-mistakes(review): Scope host hand-back failure fallback to lost pending:handling

* no-mistakes(review): Remove stray scratch test copy tests/.tmp-rest.test.sh

* no-mistakes(test): Initialise successor globals so early hand-back survives set -u

* no-mistakes(document): Document host hand-back downtime failure and Claude lost-handback notice

* no-mistakes(ci): I fixed the Greptile finding. The rule that must hold: when the supervision host hands back an actionable wake, its rewake is refused, and no watcher is healthy, the hand-back still has to reach main as a delivered notice. That must be true whether the recovery marker is `pending:handling` or `announced:handling`. Only one place applies this check: the lost hand-back fallback in `bin/fm-claude-stop-autoarm.sh`. **Fix:** that check now accepts both `pending:handling:*` and `announced:handling:*` tokens (a one-line change). Nothing else in the fallback changed: - Refusals on any other marker, such as an already acknowledged one, still exit 0 silently and open no failure episode. - The notice is still sent once per episode, and repeats are recorded as `failed-suppressed`. **Tests:** - `tests/fm-claude-stop-autoarm.test.sh`: the lost hand-back test now runs as a shared helper with two variants, one writing a `pending:handling` marker and a new one writing `announced:handling` (`test_host_lost_announced_handback_notifies_once_per_episode`). - `tests/fm-supervision-host.test.sh`: the end-to-end test where downtime restoration fails is now a shared helper too, with a new `announced` variant (`test_claude_stop_hook_notifies_when_closed_announced_successor_downtime_restore_fails`). It moves the handling episode to `announced` before the host hands back. Without the fix the hook would exit 0 here; the test requires exit 2, `outcome=failed` and a delivered failure notice. **Verification (all under nice -n 10):** - The full `tests/fm-claude-stop-autoarm.test.sh` suite passed (rc=0), including both lost hand-back variants and the benign-refusal test. - In `tests/fm-supervision-host.test.sh` I ran only the four hand-back test functions, all passing (rc=0). The suite can't run single functions, so I used a temporary copy with a trimmed test list and deleted it afterwards; `git status` shows only the 3 intended files changed. - shellcheck is clean on all three changed files. I did not run `bin/fm-lint.sh`. - I did not run the new tests against the unfixed code; the claim that they fail without the fix comes from reading the old check
* perf: cut remote-job idle process creation in the three hot loops

Post-update host measurement still attributes most idle churn to three
per-sample loops: result-consumer state reads and date calls, the delta
reader's capture/hash pass on every poll, and the lane preemption scan's
per-field pipelines. This drops each to its minimum without touching the
contracts around them.

* fm_remote_job_read_state gains an optional result-variable form backed
  by fm_remote_job_read_line, a builtin-only bounded record read (regular
  non-symlink file, byte bound, one newline-terminated line, tolerated
  unterminated tail, no carriage returns). fm_remote_job_wait samples
  state and the SECONDS clock with no per-sample children; one date call
  converts the epoch deadline once.
* fm-remote-delta-read stats the log each poll and re-runs the bounded
  capture and hashing only when size, mtime, ctime, inode, or device
  change. The snapshot's own stat writes the comparison key, so a log
  that moves between the gate and the capture is never read as stable.
* worker_preempting_waiter_exists reads state, home, and the staged argv
  head with builtins only. The now-unused worker_job_command goes away.

The bounded reads use -d '' -n, which behaves identically on the macOS
stock bash 3.2 and current bash; -N does not exist on 3.2. Tests cover
the malformed-record corpus, delta identity gating, fork-free lane
scanning through counting PATH shims, and same-home versus cross-home
preemption. No signal traps or sleep contracts change.

* no-mistakes(review): Restore subsecond delta keys and byte-bounded builtin record reads

* no-mistakes(document): Clarify delta snapshot caching and coarse-timestamp fallback

* no-mistakes(lint): Scope UTF-8 regression locales to individual function calls

* no-mistakes(ci): Fixed both lint failures by applying the documented production-library analysis boundary at the two affected test imports. Runtime behavior is unchanged; the library remains independently linted. Canonical full-analysis lint passed for the library and both suites, as did bash syntax checks and git diff --check
…ommands (kunchenguid#5963)

* fix(composer): read a titled Claude top rule as the composer's edge

A named Claude Code session draws its title into the composer's top rule.
The strict separator predicate rejected that row, so the closing rule read
as a lower unmatched separator and an idle, empty composer classified
unknown on every cursorless backend, refusing fm-send, exit, and relaunch.

Spare a bare agent-glyph row sandwiched between a width-proven titled rule
and the screen's only unmatched separator directly below it. The strict
separator predicate, dead-shell rule, and blank-row posture are unchanged.

Fixes kunchenguid#5601
Fixes kunchenguid#5558

* no-mistakes(test): Keep Claude's grey slash command in Herdr payload proof

* no-mistakes(test): Make missing-herdr version check hermetic to installed herdr
…#6387)

* fix: pre-approve Pi trust for seeded secondmate homes

Unattended first launches of Firstmate-seeded Pi secondmate homes stalled on
"Trust project folder?" until Enter. Probe --approve like --tui-mode and pass
it only for --secondmate when help advertises it (.fm-secondmate-home signal),
leaving ordinary workers and older Pi unchanged.

* no-mistakes(document): Consolidate Pi seeded-home trust documentation ownership

* no-mistakes(ci): Fixed Lint 1’s unused polling counter. Diagnosed Behavior portable serial 4 as a pre-existing delta-reader test clock race; replaced timing-dependent rewrite and deletion with deterministic executable-boundary synchronization. ShellCheck, Bash syntax checks, and git diff --check passed. Delta-reader tests passed three consecutive runs; all three live Pi trust cases passed. Production behavior unchanged
…--external-sources (kunchenguid#6443)

* fix(lint): retry memory-bound roots without external sources

* no-mistakes(review): Make fallback tests portable and correct source-following telemetry

* no-mistakes(review): Remove committed parity fixtures and use disposable test roots

* no-mistakes(document): Document ShellCheck memory fallback and telemetry

* no-mistakes(review): Cover bounded and unbounded fallback RSS behavior

* no-mistakes(document): Correct stale lint fallback documentation

* no-mistakes(document): Correct stale lint test documentation

* no-mistakes(ci): The memory fallback (the retry without --external-sources) now gets only the time left in its root's original deadline, so it can no longer outlast the CI job. Invariant: one root's first attempt plus its fallback must fit inside a single FM_LINT_ROOT_SECONDS deadline, plus the cleanup grace. Only one site started a new deadline: the fallback call in fm_lint_run_root. The deadline is the only budget involved, because the memory limit already applies to each process separately. Changes in bin/fm-lint.sh: - fm_lint_exec_root now takes a <seconds> argument instead of always reading FM_LINT_INTERNAL_ROOT_SECS. - The first attempt passes the full deadline. - The fallback passes floor((start + deadline - now) / 1000) seconds. - When bounds are enforced and less than 1 second is left, no retry starts. fm_exec_timed rejects 0 seconds, so the retry cannot run with no time. The root keeps reason=memory, and the shard output says "no time left in its Ns deadline to retry without it". - Unbounded local runs have no deadline and behave as before. - The header comment now describes the shared deadline. Changes in tests/fm-lint.test.sh: a new test, test_memory_fallback_spends_only_the_remaining_root_deadline, runs only on hosts that can enforce bounds. It uses a 6 s deadline and 1 s grace. - Case 1: the first attempt runs 3 s and then fails with memory status 251. The test asserts one fallback ran, reported reason=timeout, and the root's recorded duration is under 7000 ms. - Case 2: the first attempt runs 5.2 s. The test asserts no fallback starts, the skip is explained, and the sidecar records memory with source-following 1. Verification: - Full `nice -n 10 bash tests/fm-lint.test.sh` passed, including the new test, in about 5 minutes. - Case 1 run against the HEAD script: the root took 9168 ms, so the under-7000 ms check fails before the fix. - `bin/fm-lint.sh bin/fm-lint.sh tests/fm-lint.test.sh` reported no findings. - The CI workflow is unchanged, so the Test step still runs only tests/fm-lint.test.sh with nice -n 10 and the 12 GiB ShellCheck limit
# Conflicts:
#	tests/fm-supervision-host.test.sh
@cloud-practitioner cloud-practitioner changed the title fix: merge upstream firstmate into the fork Merge upstream firstmate into the fork Oct 3, 2026
@cloud-practitioner
cloud-practitioner merged commit 2358151 into main Oct 3, 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.

5 participants