feat(bin): wake instantly on glasses file events - #97
Merged
Conversation
Interrupt the watcher poll wait when the mailbox DB or bridge inbox changes so pending voice and photo checks run immediately instead of sitting until the next slow-check interval. Co-authored-by: Cursor <cursoragent@cursor.com>
Amplify-Logic
added a commit
that referenced
this pull request
Aug 24, 2026
* fix(fork-surface): repair glasses capability commit to the landed squash merge The glasses-file-event-wake entry still named pre-squash branch commit 74d836d, which no longer resolves anywhere in this clone or on origin, so the fork-surface check failed for every session. PR #97 landed as 9f158d3; record the same post-merge repair the bridge capability above documents. Upstream: none (fork-manifest hygiene, not a port) * fix(bin): correct session lock and attached watcher supervision (kunchenguid#1545) Port upstream 88b2a94 onto this fork's diverged supervision code, adapting rather than overwriting. Fault 1, session-lock identity and ownership. Upstream rewrote its bin/fm-session-lock-lib.sh; this fork never had that file, so the same semantics land in this fork's owners instead. bin/fm-primary-scope-lib.sh gains whole-path-component and argv[0] harness evidence (fm_harness_path_name, fm_harness_process_matches), a contiguous verified-harness ancestry walk (fm_harness_ancestry_pids), membership ownership (fm_harness_ancestry_contains), the outermost-of-run resolver (fm_harness_ancestry_pid), and a relation whose ancestry answer is membership rather than one chosen pid. bin/fm-lock.sh resolves harness_pid through that shared owner and now recognizes a live lock held inside this session's own ancestry as its own earlier acquisition, keeping the recorded owner instead of refusing or rewriting it. Fault 2, attached arms reporting delivered cycles as failures. The watcher publishes each printed reason to state/.watch-deliveries.log bound to its PID and process identity before releasing the singleton (bin/fm-watch.sh wake + bin/fm-wake-lib.sh, which owns the ledger once for watcher and arm), fm_watcher_healthy returns the matched identity, and bin/fm-watch-arm.sh tracks cycle identity and resolves an unobservable close against the delivery record: a matching record reports the delivered wake and exits zero, only a cycle that delivered nothing stays the typed nonzero failure. Covers started-child and attached closes on every harness protocol. Fork-preserving adaptations: - FM_HARNESS_RE keeps this fork's verified set (no pi-signed); loose holder-liveness survives as a boundary-checked argv-token tier so profile launchers named codex-primary still read as live holders while ordinary ~/.claude/hooks paths do not. - The ancestry walk keeps the fork's eight-parent budget, preserving documented parity with Pi's lockOwnership(). - docs/verification/supervision.md hunks skipped: this fork has no such file. - Upstream's e2e drove bin/fm-claude-stop-autoarm.sh, absent here; the end-to-end layer targets this fork's real bin/fm-lock.sh in orphaned version-named, daemon-parented, and combined process trees instead. - Hermeticity hardening for every supervision suite (incident-driven): tests/lib.sh drops inherited operational-home variables and pins a temp hermetic FM_HOME, teardown reaps only path-scoped children, and new tests/fm-supervision-test-isolation.test.sh proves a test watcher or --restart can never resolve or stop a real home. - tests/fm-test-run.sh registers the three suites in watcher-wake-lock and serial shard 2; fixture homes pinned explicitly in the affected suites per the hermeticity contract. Full bin/fm-test-run.sh --all passes at this tree (140 scripts, 0 failed); shellcheck clean via bin/fm-lint.sh on every touched script. Upstream: 88b2a94 * fix(bin): harden supervision health against fresh leftover beacons (kunchenguid#1495) Port the transferable core of upstream 33a4287: every supervision consumer now decides alarm state with the same identity-matched live-watcher and fresh-beacon predicate, so a dead or reused watcher pid with a leftover beacon can no longer read as healthy. bin/fm-guard.sh drops its beacon-fresh-only field and calls fm_watcher_healthy (bin/fm-wake-lib.sh) - the predicate the arm wrapper and turn-end guard already use. bin/fm-supervision-lib.sh's docstring records that split: banner text from the status fields, decisions from the shared predicate. Skipped as inapplicable to this fork, which has no Claude Stop auto-arm and no --claude guard mode: - bin/fm-claude-stop-autoarm.sh failure episodes, bounded retries, one-time attended fail-open, and post-alarm suppression; - lock role records (fm_lock_set_role/fm_lock_role) and fm_failure_episode_reset, whose only consumers are that subsystem; - the Claude repair-line rewording, X-mode-as-need source, and procevent sources, which would misdocument this fork's different continuity model. Regression coverage ports upstream's: a fresh beacon without a live watcher stays an alarm; healthy-recovery cases record genuinely live identity-matched holders instead of touching only the beacon; drain liveness asserts the same; secondmate config-push fixtures pin live watchers where they assert silent stderr. fork-surface.conf claims the ported surface as the supervision-recovery-port capability. Upstream: 33a4287 * fix(bin): name the true watcher-down reason and key episodes on it (kunchenguid#1661) Port the transferable core of upstream d0461e4: the pull guard's health verdict becomes model-aware and its banner names the real failing condition instead of always blaming the beacon. bin/fm-wake-lib.sh gains fm_supervision_model and fm_watcher_supervision_verdict. Under a between-turns arm-owner model (FM_SUPERVISION_MODEL=autoarm) a fresh beacon with no live watcher is healthy mid-turn and only a stale beacon alarms; under persistent models a live identity-matched watcher with a fresh beacon is still required. This fork maps every detected harness - Claude included - to the persistent model, because its Claude continuity runs tracked background arms rather than a Stop-hook auto-arm; the override keeps the model branch exercised and upstream-shaped. bin/fm-guard.sh consumes the verdict: the once-per-episode dedup keys on the failing condition instead of the beacon mtime (which any beacon touch used to churn into a fresh episode), and the banner now says whether the home lacks a live watcher process or a genuinely fresh beacon. The turn-end guard keeps the PID-strict predicate unchanged, as does the arm layer. Skipped as inapplicable: upstream's spawn-time FM_SUPERVISION_MODEL injection for secondmates (this fork launches every harness under the persistent default), the autoarm/claude-protocol doc rewrites, and the procevent/X-mode need sources this fork's guard has never had. docs/scripts.md rows for the guard and drain reflect the supervision- health wording. Upstream: d0461e4 * no-mistakes(review): Fix path-scoped test process cleanup * no-mistakes(document): Document supervision recovery contracts * no-mistakes: apply CI fixes
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Intent
Instant firstmate wake on glasses events: a file-event nudger on the mailbox DB and bridge inbox must interrupt the watcher wait instead of waiting for the next poll. When data/glasses-voice-runtime/mailbox.db (including WAL/SHM siblings) or data/bridge-inbox/ changes, the watcher's terminal wait must return immediately and expire the slow-check timer so the next authenticated check sweep, including the home-local glasses pending/photo check, runs now rather than sitting until FM_CHECK_INTERVAL. Homes without those paths must keep today's sleep POLL behavior. Herdr blocked-wake must still work when glasses paths are also watched; a file event must not count as a herdr connect failure or disable the herdr fast path. The poll loop remains the permanent fail-closed backstop. A missing or unusable file-wait falls back to sleep. This is firstmate shared tracked material.
What Changed
Risk Assessment
✅ Low: Captain, the follow-up cleanly addresses all three prior race defects while preserving the required herdr failure accounting, file-wake behavior, and polling fallback.
Testing
No pre-supplied baseline commands were available; focused and broader watcher tests passed, including mailbox/WAL/directory events, unusable-wait fallback, Herdr blocked-wake coexistence, race stderr suppression, and fast-path failure handling, while a timed manual transcript demonstrated a real bridge-inbox event returning in 0.381 seconds against a 4-second poll and homes without glasses paths retaining POLL sleep behavior.
Evidence: Timed instant-wake CLI transcript
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 3 issues found → auto-fixed ✅
bin/fm-watch.sh:976- Intent contradiction: the requirement says “a file event must not count as a herdr connect failure or disable the herdr fast path” and file changes “must interrupt the watcher wait.” If herdr returns 2 first, this branch kills the concurrent file waiter andapply_push_wait_resultperforms a blindsleep "$POLL"; a glasses event during that sleep cannot interrupt it. Preserve the failure count, but continue the remaining wait with the file-event waiter.bin/fm-watch.sh:1035- The race watchdog assumesPOLLis an integer in arithmetic expansion. Existing supported usage setsFM_POLL=0.2; when glasses paths and herdr are both active,$((POLL * 20 + 40))raises a shell arithmetic error and can terminate the watcher. Compute the deadline without integer shell arithmetic or normalize fractional values safely.bin/fm-watch.sh:1049- The post-race catch-up signature uses second-resolution mtime and size. If herdr wins while SQLite updates a mailbox file within the same second without changing its size, the killed file waiter’s event is missed and.last-checkremains unexpired. Use nanosecond metadata or preserve the file waiter’s event result before terminating it.🔧 Fix: Preserve glasses wake race across herdr failures
✅ Re-checked - no issues remain.
🔧 **Test** - 1 issue found → auto-fixed ✅
bin/fm-watch.sh:1024- When the file and Herdr waiters finish concurrently, the losing noclobber write can emitcannot overwrite existing fileto watcher stderr. The focused test reproduced this diagnostic even though the race completed correctly. Suppress the shell-level redirection error so ordinary wake races do not pollute watcher logs.Inspectedgit diff 361861c0e30c76bd5e541d3866fc53084ccc3a79..dd6d275bc7c4c0ba97c04e40fd5e3bdedb42341aagainst every authoritative acceptance constraint.bin/fm-test-run.sh tests/fm-file-eventwait.test.sh tests/fm-supervision-events.test.shManual Bash verification using the realevent_wait_or_sleep: configured an 8-second poll, created a pending bridge-inbox photo after 350ms, measured the wait returning in 415ms, and verified.last-checkwas removed.git status --shortconfirmed testing left the worktree unchanged.🔧 Fix: Suppress noclobber noise in watcher wake races
✅ Re-checked - no issues remain.
bash tests/fm-file-eventwait.test.shbash tests/fm-supervision-events.test.sh && bash tests/fm-watch-triage.test.shManual watcher-level check using the realevent_wait_or_sleepandfm-file-eventwait.py: mutateddata/bridge-inbox/during a 4-second wait, measured return latency, verified.last-checkremoval and triage logging, then removed glasses paths and measured the sleep/POLL fallback.git status --shortafter testing to confirm no working-tree artifacts remained.✅ **Document** - passed
✅ No issues found.
🔧 **Lint** - 1 issue found → auto-fixed ✅
🔧 Fix: Captain: assert fractional poll propagation in file-wait race
✅ Re-checked - no issues remain.
✅ **Push** - passed
✅ No issues found.