Skip to content

fix(bin): coalesce watcher signals into one wake - #4

Merged
kunchenguid merged 2 commits into
mainfrom
fix/watch-coalesce-signals
Jun 12, 2026
Merged

kunchenguid merged 2 commits into
mainfrom
fix/watch-coalesce-signals

Conversation

@kunchenguid

Copy link
Copy Markdown
Owner

Intent

The firstmate watcher (bin/fm-watch.sh) produced duplicate wakes: a crewmate's final status-file write and the same turn's turn-end hook land seconds apart (observed 15s), and since the watcher exits on the first changed signal file and is restarted between wakes, one logical event (crewmate finished its turn) cost two full firstmate turns. The user asked to patch this duplicate-signal behavior. Change: the signal scan is now a pure read collecting all changed files; on the first detection the watcher lingers FM_SIGNAL_GRACE (default 30s, deliberately chosen to cover the observed 15s status-to-turn-end gap), re-scans, dedupes by file, and reports everything in one 'signal: ...' wake line. Seen-signatures are deliberately written only at wake time now, so a watcher killed mid-grace cannot mark a signal seen without reporting it (the old code wrote seen before waking). AGENTS.md section 8 wording updated to match the multi-file wake format. Single-signal wake format is unchanged ('signal: '), keeping existing wake-handling instructions valid. Tested end-to-end in a sandbox: coalescing of two signals 3s apart into one wake, no duplicate wake after restart, and solo-signal wake all verified. The added wake latency (one grace period) is an accepted tradeoff; crewmate tasks run minutes to hours.

What Changed

  • Updated bin/fm-watch.sh to collect changed signal files, wait through a configurable FM_SIGNAL_GRACE window, and emit one deduped signal: <file>... wake for near-simultaneous status and turn-end signals.
  • Delayed writing seen-signal markers until the coalesced wake is reported, so watcher restarts during the grace period do not silently consume pending signals.
  • Documented the multi-file signal wake behavior in AGENTS.md and noted the default signal grace window in README.md.

Risk Assessment

✅ Low: The change is narrowly scoped to watcher signal coalescing and documentation, preserves the single-signal output shape, and I found no material correctness or operational risks in the changed code.

Testing

Exercised the actual bin/fm-watch.sh end-to-end with synthetic watcher state: coalesced status plus turn-end signals, verified no duplicate wake after restart, verified kill-during-grace does not swallow a signal, verified solo-signal output shape, captured a transcript artifact, and removed transient in-worktree state afterward.

Evidence: fm-watch coalescing transcript

Shows one wake containing both coalesced signal files, no duplicate wake after restart, preserved pending signal after kill during grace, and unchanged solo-signal wake format.

fm-watch signal coalescing end-to-end transcript
worktree: /Users/kunchen/.no-mistakes/worktrees/016d88035d58/01KTX4RWRT72G8HZECAEST4FR8
test isolation: PATH prepends fake tmux returning no fm-* windows, avoiding unrelated stale wakes from live sessions.

Scenario 1: status write and turn-end marker 1s apart coalesce into one wake.
wake: signal: /Users/kunchen/.no-mistakes/worktrees/016d88035d58/01KTX4RWRT72G8HZECAEST4FR8/state/demo.status /Users/kunchen/.no-mistakes/worktrees/016d88035d58/01KTX4RWRT72G8HZECAEST4FR8/state/demo.turn-ended 
result: PASS - one wake contained both changed signal files.

Scenario 2: restarting after the coalesced wake does not produce a duplicate signal wake.
wake: <none within 3s>
result: PASS - seen signatures suppressed duplicate wake after restart.

Scenario 3: a watcher killed during the grace period does not mark a signal seen before reporting it.
wake after restart: signal: /Users/kunchen/.no-mistakes/worktrees/016d88035d58/01KTX4RWRT72G8HZECAEST4FR8/state/grace.status 
result: PASS - killed watcher did not swallow the pending signal.

Scenario 4: a solo status signal keeps the original single-file wake shape.
wake: signal: /Users/kunchen/.no-mistakes/worktrees/016d88035d58/01KTX4RWRT72G8HZECAEST4FR8/state/solo.status 
result: PASS - solo signal reported as one signal wake with one file.

Pipeline

Updates from git push no-mistakes

✅ **intent** - passed

✅ No issues found.

✅ **Rebase** - passed

✅ No issues found.

✅ **Review** - passed

✅ No issues found.

✅ **Test** - passed

✅ No issues found.

  • FM_SIGNAL_GRACE=2 FM_POLL=1 FM_HEARTBEAT=9999 FM_HEARTBEAT_MAX=9999 FM_CHECK_INTERVAL=9999 bin/fm-watch.sh with a status write followed by a turn-end marker 1s later, isolated from live tmux windows by a fake tmux on PATH
  • FM_SIGNAL_GRACE=1 FM_POLL=1 FM_HEARTBEAT=9999 FM_HEARTBEAT_MAX=9999 FM_CHECK_INTERVAL=9999 bin/fm-watch.sh restarted after the coalesced wake and observed for 3s to confirm no duplicate signal wake
  • FM_SIGNAL_GRACE=5 ... bin/fm-watch.sh killed during the grace period, followed by a restart with FM_SIGNAL_GRACE=1, to confirm pending signals are not marked seen before a wake is reported
  • FM_SIGNAL_GRACE=1 ... bin/fm-watch.sh with a single status-file signal to confirm the original single-file signal: &lt;file&gt; wake shape remains intact
✅ **Document** - passed

✅ No issues found.

✅ **Lint** - passed

✅ No issues found.

✅ **Push** - passed

✅ No issues found.

A crewmate's final status write and the same turn's turn-end hook land
seconds apart, producing two wakes for one logical event - each costing a
full firstmate turn. On the first changed signal the watcher now lingers
FM_SIGNAL_GRACE (default 30s), re-scans, and reports every changed file in
a single wake line. Seen-signatures are written only when a wake is
actually reported, so a watcher killed mid-cycle can no longer mark a
signal seen without delivering it.
@kunchenguid
kunchenguid merged commit 9931f81 into main Jun 12, 2026
@kunchenguid
kunchenguid deleted the fix/watch-coalesce-signals branch June 12, 2026 05:37
@frederikwalbech

Copy link
Copy Markdown

Reviewed clean by fresh reviewer (kb-i4-rev-s2: e2e maintenance CLI + demo-run inspection + full harness green) and merged into integration branch fm/npt-build-s8 (merge 2254afa; additive conflicts vs #3 union-resolved). Lands in the single batch PR to main.

@darwinposo

Copy link
Copy Markdown

Check state - correction to the Intent section above

The Intent section above, carried over from the task brief, states that GitHub Actions is permanently billing-refused on this repository and that 0 passed, 0 failed, no CI checks configured is the expected artifact.
That premise did not hold on this run and is corrected here. CI genuinely ran.

The verified state on head 4700f6b is 9 passed, 1 failed. That is not green, and it is not the billing artifact either.

Check Result
Behavior portable serial failure
Behavior timing aggregate success
Behavior portable parallel 1 success
Behavior portable parallel 2 success
Behavior tests (Herdr) success
Lint shell scripts success
Test coverage guard success
Repo invariants success
Stock macOS Bash snapshot compatibility success
PR must be raised via no-mistakes success

The single failure, Behavior portable serial, is pre-existing and not caused by this change.
On origin/main (0d8f0b8) all 9 checks fail, including that one.
This branch's only difference from origin/main is one added file under docs/, which no test reads, so the failure is origin/main's behaviour rather than a regression introduced here.
Fixing it is out of scope for a document placement.

Two honest caveats worth recording:

  • gh pr checks 4 summarizes this PR as 0 passed, 0 failed - this PR has no CI checks configured, which contradicts the check-runs API showing 10 completed checks. The API is the accurate source; the summary is misleading here.
  • The pipeline's three CI auto-fix attempts each aborted on an agent session-quota limit rather than on the code, so no fix was actually attempted and none was applied.

No check was relaxed, disabled, or deleted. Merge evidence remains the three SHA-256 verifications, the supersession sweep, and the local suite counts.

vipentti pushed a commit to vipentti/firstmate that referenced this pull request Aug 5, 2026
Add cursor-agent CLI as a verified firstmate crewmate/secondmate adapter.

Phase 1 verification (2026-08-04):
- Hooks: no hooks could be established with any tested manifest
  format (phase 3, pending correct upstream manifest key discovery).
- Raw-launch smoke: reproduced all 4 defects (composer, liveness,
  CLAUDECODE leak, no busy source).
- Steering: Enter-while-busy queues the key (same as opencode 1.18.4,
  no separate submit core branch needed). /no-mistakes lands on
  single Enter with popup settle.

Phase 2 implementation (this PR):
- Detection: CURSOR_AGENT=1 env marker ordered before CLAUDECODE
  (fixes defect kunchenguid#3: CLAUDECODE leak). Ancestry backstop for
  MainThread/cursor-agent args.
- Liveness: cursor-agent in FM_HARNESS_RE, FM_HARNESS_NAMES,
  tmux agent classification (fixes defect kunchenguid#2: liveness).
- Composer: → glyph, Add a follow-up idle regex, reverse-video
  cursor ghost-gap fix (fixes defect kunchenguid#1: pending idle pane).
- Busy state: unknown cursor-unverified with empty version gate
  (captain decision 2026-08-04, option a). No rendered-signal
  fallback (fixes defect kunchenguid#4: no busy source).
- Launch: cursor in allowlists, launch template with --force --trust
  --model, env sanitization (unset CLAUDECODE), fail-closed binary
  check, effort recorded in meta but not emitted.
- Interrupt: C-c (not Escape, verified).
- Docs: harness-adapters/SKILL.md cursor section, AGENTS.md §4,
  docs/configuration.md, docs/turnend-guard.md, verification evidence.
- Tests: fm-cursor-harness.test.sh (new), extensions to
  fm-composer-lib, fm-composer-ghost, fm-tmux-agent-liveness,
  fm-busy-state, fm-busy-adapter-wiring.

Out of scope: firstmate primary on cursor, herdr-backend cursor,
cursor --worktree, phase 3 hooks, effort-suffix composition.

Reference: kunchenguid#771
vipentti added a commit to vipentti/firstmate that referenced this pull request Aug 5, 2026
Add cursor-agent CLI as a verified firstmate crewmate/secondmate adapter.

Phase 1 verification (2026-08-04):
- Hooks: no hooks could be established with any tested manifest
  format (phase 3, pending correct upstream manifest key discovery).
- Raw-launch smoke: reproduced all 4 defects (composer, liveness,
  CLAUDECODE leak, no busy source).
- Steering: Enter-while-busy queues the key (same as opencode 1.18.4,
  no separate submit core branch needed). /no-mistakes lands on
  single Enter with popup settle.

Phase 2 implementation (this PR):
- Detection: CURSOR_AGENT=1 env marker ordered before CLAUDECODE
  (fixes defect kunchenguid#3: CLAUDECODE leak). Ancestry backstop for
  MainThread/cursor-agent args.
- Liveness: cursor-agent in FM_HARNESS_RE, FM_HARNESS_NAMES,
  tmux agent classification (fixes defect kunchenguid#2: liveness).
- Composer: → glyph, Add a follow-up idle regex, reverse-video
  cursor ghost-gap fix (fixes defect kunchenguid#1: pending idle pane).
- Busy state: unknown cursor-unverified with empty version gate
  (captain decision 2026-08-04, option a). No rendered-signal
  fallback (fixes defect kunchenguid#4: no busy source).
- Launch: cursor in allowlists, launch template with --force --trust
  --model, env sanitization (unset CLAUDECODE), fail-closed binary
  check, effort recorded in meta but not emitted.
- Interrupt: C-c (not Escape, verified).
- Docs: harness-adapters/SKILL.md cursor section, AGENTS.md §4,
  docs/configuration.md, docs/turnend-guard.md, verification evidence.
- Tests: fm-cursor-harness.test.sh (new), extensions to
  fm-composer-lib, fm-composer-ghost, fm-tmux-agent-liveness,
  fm-busy-state, fm-busy-adapter-wiring.

Out of scope: firstmate primary on cursor, herdr-backend cursor,
cursor --worktree, phase 3 hooks, effort-suffix composition.

Reference: kunchenguid#771
vipentti pushed a commit to vipentti/firstmate that referenced this pull request Aug 5, 2026
* fix(watch): coalesce near-simultaneous signals into one wake

A crewmate's final status write and the same turn's turn-end hook land
seconds apart, producing two wakes for one logical event - each costing a
full firstmate turn. On the first changed signal the watcher now lingers
FM_SIGNAL_GRACE (default 30s), re-scans, and reports every changed file in
a single wake line. Seen-signatures are written only when a wake is
actually reported, so a watcher killed mid-cycle can no longer mark a
signal seen without delivering it.

* no-mistakes(document): Document watcher signal grace
vipentti added a commit to vipentti/firstmate that referenced this pull request Aug 5, 2026
Add cursor-agent CLI as a verified firstmate crewmate/secondmate adapter.

Phase 1 verification (2026-08-04):
- Hooks: no hooks could be established with any tested manifest
  format (phase 3, pending correct upstream manifest key discovery).
- Raw-launch smoke: reproduced all 4 defects (composer, liveness,
  CLAUDECODE leak, no busy source).
- Steering: Enter-while-busy queues the key (same as opencode 1.18.4,
  no separate submit core branch needed). /no-mistakes lands on
  single Enter with popup settle.

Phase 2 implementation (this PR):
- Detection: CURSOR_AGENT=1 env marker ordered before CLAUDECODE
  (fixes defect kunchenguid#3: CLAUDECODE leak). Ancestry backstop for
  MainThread/cursor-agent args.
- Liveness: cursor-agent in FM_HARNESS_RE, FM_HARNESS_NAMES,
  tmux agent classification (fixes defect kunchenguid#2: liveness).
- Composer: → glyph, Add a follow-up idle regex, reverse-video
  cursor ghost-gap fix (fixes defect kunchenguid#1: pending idle pane).
- Busy state: unknown cursor-unverified with empty version gate
  (captain decision 2026-08-04, option a). No rendered-signal
  fallback (fixes defect kunchenguid#4: no busy source).
- Launch: cursor in allowlists, launch template with --force --trust
  --model, env sanitization (unset CLAUDECODE), fail-closed binary
  check, effort recorded in meta but not emitted.
- Interrupt: C-c (not Escape, verified).
- Docs: harness-adapters/SKILL.md cursor section, AGENTS.md §4,
  docs/configuration.md, docs/turnend-guard.md, verification evidence.
- Tests: fm-cursor-harness.test.sh (new), extensions to
  fm-composer-lib, fm-composer-ghost, fm-tmux-agent-liveness,
  fm-busy-state, fm-busy-adapter-wiring.

Out of scope: firstmate primary on cursor, herdr-backend cursor,
cursor --worktree, phase 3 hooks, effort-suffix composition.

Reference: kunchenguid#771
vipentti added a commit to vipentti/firstmate that referenced this pull request Aug 5, 2026
Add cursor-agent CLI as a verified firstmate crewmate/secondmate adapter.

Phase 1 verification (2026-08-04):
- Hooks: no hooks could be established with any tested manifest
  format (phase 3, pending correct upstream manifest key discovery).
- Raw-launch smoke: reproduced all 4 defects (composer, liveness,
  CLAUDECODE leak, no busy source).
- Steering: Enter-while-busy queues the key (same as opencode 1.18.4,
  no separate submit core branch needed). /no-mistakes lands on
  single Enter with popup settle.

Phase 2 implementation (this PR):
- Detection: CURSOR_AGENT=1 env marker ordered before CLAUDECODE
  (fixes defect kunchenguid#3: CLAUDECODE leak). Ancestry backstop for
  MainThread/cursor-agent args.
- Liveness: cursor-agent in FM_HARNESS_RE, FM_HARNESS_NAMES,
  tmux agent classification (fixes defect kunchenguid#2: liveness).
- Composer: → glyph, Add a follow-up idle regex, reverse-video
  cursor ghost-gap fix (fixes defect kunchenguid#1: pending idle pane).
- Busy state: unknown cursor-unverified with empty version gate
  (captain decision 2026-08-04, option a). No rendered-signal
  fallback (fixes defect kunchenguid#4: no busy source).
- Launch: cursor in allowlists, launch template with --force --trust
  --model, env sanitization (unset CLAUDECODE), fail-closed binary
  check, effort recorded in meta but not emitted.
- Interrupt: C-c (not Escape, verified).
- Docs: harness-adapters/SKILL.md cursor section, AGENTS.md §4,
  docs/configuration.md, docs/turnend-guard.md, verification evidence.
- Tests: fm-cursor-harness.test.sh (new), extensions to
  fm-composer-lib, fm-composer-ghost, fm-tmux-agent-liveness,
  fm-busy-state, fm-busy-adapter-wiring.

Out of scope: firstmate primary on cursor, herdr-backend cursor,
cursor --worktree, phase 3 hooks, effort-suffix composition.

Reference: kunchenguid#771
@pruge

pruge commented Aug 24, 2026

Copy link
Copy Markdown

Rebased onto current main (0a2f59e): resolved the documentation-audiences.json overlap by keeping task-grill's entries alongside task-design, and merged the AGENTS.md planning-family exception to cover task-planning, task-grill, and task-design with design documents included. The redundant task-planning audience-classification commit collapsed during rebase since main already carries those entries. Lint and documentation-audience checks pass; content unchanged from the previously validated pipeline run.

davestanyer pushed a commit to davestanyer/firstmate that referenced this pull request Sep 17, 2026
…3-windows

merge: sync windows with upstream kunchenguid/firstmate main (1c5c9c1)
NewAiCoder referenced this pull request in NewAiCoder/firstmate Sep 26, 2026
* fix(bin): forbid manual gh issue close/reopen and gh project in ship briefs

Pipeline seats hand-closing issues (kunchenguid#1222, kunchenguid#1229) with three gh issue
close calls each under a throttled account tripped the fleet-wide
GraphQL abuse limiter. The PR body's closes #N already closes issues
on merge, and project boards are ruled out entirely, so manual closes
and any gh project command are pure churn.

Adds one rule to the ship scaffold in bin/fm-brief.sh and a matching
--intent carry-forward clause in the no-mistakes DOD (bin/fm-dod-lib.sh)
so pipeline seats inherit it. Extends tests/fm-brief.test.sh to assert
the rule text across all three ship delivery modes and confirm scout
briefs do not carry it.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Uj566GMjYRLax7NMbU5gpw

* no-mistakes(review): Propagate GitHub command ban through scout promotions

---------

Co-authored-by: NewAiCoder <170579485+NewAiCoder@users.noreply.github.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
NewAiCoder referenced this pull request in NewAiCoder/firstmate Sep 26, 2026
* fix(bin): forbid manual gh issue close/reopen and gh project in ship briefs

Pipeline seats hand-closing issues (kunchenguid#1222, kunchenguid#1229) with three gh issue
close calls each under a throttled account tripped the fleet-wide
GraphQL abuse limiter. The PR body's closes #N already closes issues
on merge, and project boards are ruled out entirely, so manual closes
and any gh project command are pure churn.

Adds one rule to the ship scaffold in bin/fm-brief.sh and a matching
--intent carry-forward clause in the no-mistakes DOD (bin/fm-dod-lib.sh)
so pipeline seats inherit it. Extends tests/fm-brief.test.sh to assert
the rule text across all three ship delivery modes and confirm scout
briefs do not carry it.

* no-mistakes(review): Propagate GitHub command ban through scout promotions

---------
Grijak added a commit to Grijak/firstmate that referenced this pull request Sep 28, 2026
chore: merge upstream firstmate main into the fork
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.

4 participants