fix(supervision): probe stat flavor at runtime instead of branching on uname#734
Open
kennyg wants to merge 4 commits into
Open
fix(supervision): probe stat flavor at runtime instead of branching on uname#734kennyg wants to merge 4 commits into
kennyg wants to merge 4 commits into
Conversation
GNU coreutils stat ahead of /usr/bin/stat on PATH (a common Homebrew layout) makes the old uname=Darwin branch pick the BSD `-f` form, which GNU stat reads as its filesystem-mode flag and answers with a multi-line "File: ..." dump. Under set -u that stray string crashed the watcher with "File: unbound variable" and took supervision down (issue kunchenguid#464). Add bin/fm-stat-lib.sh as the single owner of a portable file-mtime read that detects GNU vs BSD stat by probing `stat -c %Y /` once, never by OS. Rewire bin/fm-supervision-lib.sh (the reported crash site) to source it. The other uname-based stat helpers are deliberately left for follow-ups so this stays a single-hot-file change.
5 tasks
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
Fix upstream issue #464: on macOS, when GNU coreutils' stat is ahead of /usr/bin/stat on PATH (a common Homebrew layout), firstmate's supervision helpers picked the BSD 'stat -f' form based on uname=Darwin. GNU stat reads -f as its filesystem-mode flag and emits a multi-line 'File: ...' dump instead of an epoch, and under 'set -u' that stray string crashed the watcher with 'File: unbound variable', taking supervision down.
The fix replaces OS-based (uname) stat-flavor detection with a runtime capability probe. Scope is deliberately narrow (PR 1 of a planned series, shaped to touch as few existing files as possible so it stays rebase-conflict-resistant against an actively-changing upstream):
Deliberately NOT touching bin/fm-wake-lib.sh, bin/fm-lock-lib.sh, bin/fm-watch.sh, or bin/fm-supervise-daemon.sh: they keep their current uname branch, and migrating them to the shared helper is intentionally deferred to follow-up PRs to keep this a single-existing-hot-file change. One necessary consequence: tests/fm-turnend-guard.test.sh copies supervision scripts into a temp bin, so a line was added to copy the new fm-stat-lib.sh dependency alongside them. The PR must open against the parent repo kunchenguid/firstmate from the fork.
What Changed
bin/fm-stat-lib.sh, a sourceable helper whosefm_stat_mtimereads a file's epoch mtime by probingstat -c %Y /once (caching the result inFM_STAT_FLAVOR) and falling back to BSDstat -f %m, never branching onuname.bin/fm-supervision-lib.sh(and itsfm-guard.shcaller) to source the shared helper and callfm_stat_mtime, replacing the OS-based stat-flavor branch that crashed the watcher with an unbound-variable error when GNU coreutils'statpreceded/usr/bin/staton macOS PATH.tests/fm-stat-lib.test.shcovering a valid epoch read, real-mtime tracking, and empty output for a missing path; documented the helper indocs/scripts.md; and copied the new dependency into the temp bins used bytests/fm-backend.test.shandtests/fm-turnend-guard.test.sh.Risk Assessment
✅ Low: Small, well-bounded refactor extracting a capability-probed portable mtime helper; the sole round-1 error (dangling function call) is fixed, the probe logic is correct across GNU/BSD/busybox, and the intentionally-deferred uname sites are documented and unchanged.
Testing
Completed 1 recorded test check.
Pipeline
Updates from git push no-mistakes
✅ **intent** - passed
✅ No issues found.
✅ **Rebase** - passed
✅ No issues found.
🔧 **Review** - 2 issues found → auto-fixed ✅
bin/fm-guard.sh:51- bin/fm-guard.sh:51 still calls fm_sup_stat_mtime, but this PR deleted that function from fm-supervision-lib.sh (which fm-guard.sh sources at line 42) and replaced it with fm_stat_mtime. fm-guard.sh has no local definition of fm_sup_stat_mtime, so fm_guard_stale_episode_key now invokes an undefined function: underset -u(noset -e) it does not abort, but it emits a 'fm_sup_stat_mtime: command not found' to stderr on every call and leavesmempty. The episode key therefore collapses to a constant 'beat:unknown' whenever the beacon exists, defeating the mtime-based staleness-episode dedup (a recovered-then-restale beacon no longer earns a fresh full banner; distinct episodes are merged). fm_stat_mtime is already in scope here transitively, so the fix is renaming the call at line 51 to fm_stat_mtime.bin/fm-stat-lib.sh:30- fm-stat-lib.sh's header claims flavor detection 'runs at most once per shell' via the FM_STAT_FLAVOR cache, but every caller invokes it through command substitution (e.g.m=$(fm_stat_mtime "$beat")), so the FM_STAT_FLAVOR assignment happens in a subshell and never persists to the parent. Each mtime read thus re-probesstat -c %Y /. Impact is negligible (one extra cheap stat per call on an infrequent path), but the stated caching guarantee is not actually achieved with the current call sites.🔧 Fix: fix fm-guard stat call and correct stat-lib cache comment
✅ Re-checked - no issues remain.
⏭️ **Test** - skipped
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"🔧 Fix: copy fm-stat-lib.sh into fm-backend test's old bin
1 error still open:
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"🔧 Fix: no fix needed; remaining failures are pre-existing env issues
1 error still open:
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"🔧 Fix: no fix needed; remaining failures are pre-existing env issues
1 error still open:
command -v tmux >/dev/null || { echo "tmux is required for e2e tests" >&2; exit 1; }; tmux -V; rc=0; for t in tests/*.test.sh; do echo "== $t =="; bash "$t" || rc=1; done; exit "$rc"✅ **Document** - passed
✅ No issues found.
✅ **Lint** - passed
✅ No issues found.
✅ **Push** - passed
✅ No issues found.