Skip to content

fix: GNU-first stat in file_mtime() to unbreak GNU/Linux CI (#245)#246

Merged
kiki830621 merged 1 commit into
mainfrom
idd/245-file-mtime-gnu-first
Jul 6, 2026
Merged

fix: GNU-first stat in file_mtime() to unbreak GNU/Linux CI (#245)#246
kiki830621 merged 1 commit into
mainfrom
idd/245-file-mtime-gnu-first

Conversation

@kiki830621

Copy link
Copy Markdown
Member

Problem

CI is red on main: the idd-tree-lock fixture suite fails on the GNU/Linux
runner (green on macOS) —

✗ f8 fresh unreadable lock -> held (not stolen): expected exit 3 got 1

A red main obscures the CI signal for every future PR, so this is the
highest-priority fix in the backlog.

Root cause — BSD-first stat breaks on GNU/Linux

scripts/idd-tree-lock.sh file_mtime() tried BSD syntax first:

file_mtime() { stat -f %m "$1" 2>/dev/null || stat -c %Y "$1" 2>/dev/null; }

The two stat dialects read the flags incompatibly:

flag BSD / macOS GNU / coreutils (Linux CI)
-f format string (%m = mtime) --file-system (prints multi-line fs status)
-c illegal option → clean usage error, no stdout format string (%Y = mtime)

On Linux, stat -f %m "$LOCK" is parsed as --file-system and prints the
lock's multi-line filesystem block to stdout. file_mtime() therefore returns
"<multi-line fs garbage>\n<real mtime>", which corrupts holder_is_live()'s
freshness arithmetic (age=$(( now - mtime )) on a multi-line string fails →
holder judged "not live" → a fresh empty lock is wrongly reclaimed instead
of held). Hence expected exit 3 got 1.

Fix

Try GNU-first, which resolves cleanly on both platforms:

file_mtime() { stat -c %Y "$1" 2>/dev/null || stat -f %m "$1" 2>/dev/null; }
  • GNU/Linux: stat -c %Y succeeds → bare integer, || short-circuits (no fs garbage ever produced).
  • BSD/macOS: stat -c %Y fails cleanly (illegal option, no stdout — verified rc=1, empty stdout) → || falls through to stat -f %m → correct mtime.

Test — RED/GREEN regression

New fixture f11 PATH-shims a GNU-semantics stat so the Linux-only failure
reproduces on any host: RED with the BSD-first order (reproduces the exact
expected exit 3 got 1 symptom on macOS), GREEN after the swap.

  • Before fix: idd-tree-lock 20 PASS / 1 FAIL (f11).
  • After fix: idd-tree-lock 21 PASS / 0 FAIL.
  • Full CI entrypoint (run-all-tests.sh): 21 suites, 0 failed.

Verification

  • bash plugins/issue-driven-dev/scripts/run-all-tests.sh → 21 suites, 0 failed (local macOS)
  • CI Linux runner green on this branch ← the cross-platform check I can't run locally

Refs #245

idd-tree-lock file_mtime() tried BSD `stat -f %m` first. On GNU/Linux `-f`
is --file-system, so it leaks a multi-line filesystem block into
holder_is_live()`s freshness arithmetic; a fresh empty lock is then wrongly
reclaimed instead of held. That broke the f8 fixture only on the Linux CI
runner (green on macOS), leaving main red.

Swap to GNU-first `stat -c %Y || stat -f %m`: on Linux the first form
succeeds and short-circuits (no fs garbage); on BSD/macOS `-c` is illegal ->
clean rc=1 empty stdout -> falls through to `-f %m`. Add regression fixture
f11 that PATH-shims a GNU-semantics stat so the Linux-only failure is now
reproducible on any host (RED before, GREEN after). Full suite 21/21.

Refs #245
@kiki830621 kiki830621 merged commit 3faac4e into main Jul 6, 2026
1 check passed
@kiki830621 kiki830621 deleted the idd/245-file-mtime-gnu-first branch July 6, 2026 11:53
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.

1 participant