Skip to content

feat: stage-readiness.sh — pre-flight checklist for ICLR talk - #412

Merged
sonichi merged 2 commits into
mainfrom
feat/stage-readiness
Apr 17, 2026
Merged

sonichi merged 2 commits into
mainfrom
feat/stage-readiness

Conversation

@sonichi

@sonichi sonichi commented Apr 17, 2026

Copy link
Copy Markdown
Owner

Summary

Mini overnight deliverable #3 of 4 for the ICLR talk (stacked on #411's presenter-mode.sh — this script references but doesn't require it).

Runs 8 checks in <20s and prints PASS/WARN/FAIL for each. Exits 1 on any FAIL, 2 on any WARN, 0 on all-PASS — integrates cleanly with CI / launchd / other pre-flight automation.

Checks

name what it verifies
voice-agent port 9900 listening + log mtime <60s
bodhi state machine 0× CLOSED→RECONNECTING FATALs in current log (post-#409)
conversation-server port 3100 /health = ok
ngrok tunnel curl public URL, expect 200-range
presenter-mode sentinel present + future expiry (mutes notifications)
Claude quota >=10% remaining
disk space >5 GB free on /
memory-sync last sync <6h

Design

  • No interactive prompts — runs headless in the 5-10 min pre-talk window.
  • Colored output by default; -q / --quiet suppresses PASS lines.
  • Exit code distinguishes warn (2) vs fail (1) so callers can auto-block on FAIL but still proceed on WARN.
  • BSD (macOS) and GNU stat -c/-f %m/%Y both handled.

Local test result (Mac Mini, 2026-04-16)

Summary: 5 pass, 3 warn, 0 fail

Warnings observed locally, all actionable:

  • ngrok tunnel unreachable — curl exit 35 (SSL handshake) to the public URL. Separate bug, non-blocking for remote ICLR talk (Zoom audio routing, not phone). Flagged for followup.
  • presenter-mode INACTIVE — expected; bash scripts/presenter-mode.sh start before talk.
  • memory-sync 9h — bash src/sync-memory.sh before talk.

Not in scope (deliverable #4)

Live latency-observability tail during the talk. Separate PR.

🤖 Generated with Claude Code

Mini overnight deliverable #3 of 4. Runs ~8 checks in <20s and prints
PASS/WARN/FAIL for each. Exits 1 on any FAIL, 2 on any WARN, 0 on all-PASS
so it integrates cleanly with other scripts / CI.

Checks:
  voice-agent          port 9900 listening + log mtime <60s
  bodhi state machine  0× CLOSED→RECONNECTING FATALs in current log (post-#409)
  conversation-server  port 3100 /health = ok
  ngrok tunnel         curl public URL, expect 200-range
  presenter-mode       sentinel present + future expiry
  Claude quota         >=10% remaining
  disk space           >5 GB free
  memory-sync          last sync <6h

Designed for the 5-10 min pre-talk window. Owner runs it once, sees the
summary, triages the WARNs before going on screen.

Local test: 5 pass, 3 warn, 0 fail. Warnings are all actionable:
- ngrok tunnel unreachable (SSL handshake fails — separate bug, non-blocking
  for remote ICLR talk which uses Zoom audio routing, not phone)
- presenter-mode INACTIVE — expected, run before talk
- memory-sync 9h — run `bash src/sync-memory.sh` before talk

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@sonichi

sonichi commented Apr 17, 2026

Copy link
Copy Markdown
Owner Author

Cold review — 2 bugs on dev Macs with Homebrew coreutils in PATH

Ran the script on MacBook (my environment; this is where the Apr 26 pre-flight will run) and hit two portability bugs. Both are caused by GNU coreutils (stat, df) being in PATH — a very common Mac dev setup via Homebrew. Mini's reported clean test likely came from a pure-BSD-stat env.

Bug 1: stat -f %m fails loudly on GNU stat (line 42, line 133)

GNU stat -f is --file-system, not BSD's "format". stat -f %m FILE on GNU:

  • Writes filesystem info like File: "..."\n ID: ...\n ... to stdout
  • Exits 1 → triggers the || stat -c %Y fallback
  • The || fallback appends the epoch to stdout

Combined stdout becomes a multi-line string like:

  File: "/path/..."
    ID: ...
    ...
1776423934

Fed into $(( date - CAPTURE )), bash with set -u tries to interpret the leading File: as a var dereference → fatal error, aborts remaining checks.

Reproduction on my Mac:

/tmp/stage-readiness.sh: line 133: File: unbound variable

…and all checks after #8 silently never ran because the script died.

Fix: try GNU (-c %Y) first — it's the more restrictive format. BSD stat rejects -c cleanly without spilling stdout:

age_sec=$(($(date +%s) - $(stat -c %Y "$SYNC_HEAD" 2>/dev/null || stat -f %m "$SYNC_HEAD")))

Same fix needed at line 42 (voice-agent freshness check).

Bug 2: df -g fails silently on GNU df (line 121)

GNU df doesn't accept -g. Output in my env:

df: invalid option -- 'g'

stdout empty → AVAIL_GB="" → [ -n "${AVAIL_GB:-}" ] is FALSE → disk check silently skipped. No PASS, no WARN, no FAIL — nothing prints. Silent failure is worse than loud failure for a pre-flight script.

Fix: probe with GNU first, fall back to BSD:

AVAIL_GB=$(df --block-size=1G / 2>/dev/null | awk 'NR==2 {print $4}' | tr -d 'G')
[ -z "$AVAIL_GB" ] && AVAIL_GB=$(df -g / 2>/dev/null | awk 'NR==2 {print $4}')

Minor: dead conditional on ngrok probe (line 83)

[[ "$probe_url" != */* ]] || probe_url="${NGROK_URL%/}/health"

*/* matches any string containing /. Every URL contains :// so the pattern always matches; != is always FALSE; the || branch always fires. Intended logic ("append /health only if URL has no path") doesn't work — it's always appended. Since always-/health is what the comment says you want, the simpler form is:

probe_url="${NGROK_URL%/}/health"

Impact

Without fix: on any Mac with Homebrew coreutils (including the MacBook that may run Apr 26 pre-flight), stage-readiness.sh dies at the memory-sync check with a cryptic unbound-variable error and skips 2+ checks silently. Not ideal for the 5-10 min pre-flight window.

Recommendation

Fix the three sites above and re-test on a Mac with /opt/homebrew/opt/coreutils/libexec/gnubin/ in PATH (or just PATH="/opt/homebrew/opt/coreutils/libexec/gnubin:$PATH" bash scripts/stage-readiness.sh). Repro one-liner:

which stat  # if this returns a path under /opt/homebrew, you have GNU stat

Cold review: 2 findings (both latent, both would manifest on MacBook).

— Reviewed from MacBook, 2026-04-17. PRs #411 and #413 are clean.

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cold-review findings (formal review — content is in issue comment at #412 (comment)):

2 real bugs on any Mac with Homebrew coreutils in PATH:

  1. stat -f %m on GNU stat spills filesystem-info garbage to STDOUT (not just stderr) + exits 1, triggering the || stat -c %Y fallback. Combined stdout (including File: ... prefix) gets fed into $(( date - CAPTURE )) where bash with set -u chokes on File: as an unbound-var reference. Script dies line 133, silently skipping checks 2+. Reproduced locally.
  2. df -g isn't supported on GNU df. Silently produces empty stdout → disk check skipped with no PASS/WARN/FAIL emitted.

1 dead conditional: [[ "$probe_url" != */* ]] || probe_url=... on ngrok always matches (URLs always have /), so the || branch always fires — intended "append /health only if no path" doesn't work.

Fix is small (try GNU syntax first, BSD fallback; or use POSIX-portable forms). MacBook where Apr 26 pre-flight will run has coreutils in PATH → this must be fixed before the talk.

Three real bugs surfaced by MacBook's cold-review on a Mac-with-Homebrew-
coreutils dev environment (the common setup — MacBook's Apr 26 pre-flight
will run under this shell).

1. `stat -f %m` BSD syntax fails silently on GNU coreutils: GNU emits
   filesystem info to stdout AND exits non-zero. The original
   `BSD || GNU` fallback in `$(...)` concatenated both outputs, then
   `set -u` arithmetic expansion died with "File: unbound variable"
   and silently aborted remaining checks. Fix: new `stat_mtime()` helper
   tries GNU first (works on Linux + coreutils-on-Mac), BSD as fallback,
   explicit `echo 0` as second fallback. Order matters — GNU first is
   correct because GNU's presence is the bug trigger; pure BSD Macs
   without coreutils work cleanly either way.

2. `df -g` is BSD-only. GNU df doesn't support `-g`, so the disk check
   silently printed nothing and was skipped entirely. Fix: POSIX
   `df -k` returns KB universally; convert to GB via arithmetic.

3. Dead `[[ "$probe_url" != */* ]]` conditional on the ngrok probe:
   the pattern matches any URL containing `/` — i.e., every full URL,
   because `https://` has slashes — so the branch always triggers
   regardless of path. The intended "append /health only if no path"
   semantic doesn't work. Fix: just unconditionally append
   `/health` since all Twilio-bound ngrok webhooks need it.

Tested on this Mac: `bash scripts/stage-readiness.sh` → 6 pass, 2 warn
(ngrok SSL handshake + presenter-mode INACTIVE are expected pre-talk),
exit 0.

Credit: MacBook's cold-review-log.md entry on #412 caught (1) and (2);
(3) I'd already flagged in build_log pass 435 but hadn't fixed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

@sonichi sonichi left a comment

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Follow-up review: fix commit 5d7eaaf verified on MacBook. Before the fix, the script died mid-stream at line 133 (File: unbound variable) and silently skipped the disk check. After the fix:

Summary: 6 pass, 1 warn, 1 fail

All 8 checks now run to completion. The stat_mtime() helper is cleaner than the inline GNU||BSD pattern — nice. POSIX df -k / | awk '{print $4}' with arithmetic is portable. Dead conditional gone.

The 1 FAIL hit is pre-existing (89 cumulative bodhi FATALs in the log from before de64333 landed) — not a fix regression; restart voice-agent before Apr 26 will clear it. The 1 WARN is presenter-mode not active, which is correct (you only start it during the actual talk window).

LGTM — ready to merge.

@sonichi
sonichi merged commit 1a9894b into main Apr 17, 2026
@sonichi
sonichi deleted the feat/stage-readiness branch April 17, 2026 12:53
sonichi pushed a commit that referenced this pull request Apr 17, 2026
`sed -i '' ...` is BSD-only syntax. On Macs with Homebrew gnu-sed
ahead of BSD sed in PATH (common dev setup), GNU sed interprets the
empty string `''` as an input filename rather than an in-place
argument, so the rewrite silently fails and `.env` is left with the
stale WEBHOOK_BASE_URL. Next `src/startup.sh` run would restart
services pointed at the wrong ngrok URL.

Fix: tmpfile + mv. Works on both BSD and GNU sed without in-place
gymnastics; no `-i` flag at all.

Same bug class as the PR #412 cold-review findings (`stat -f %m`,
`df -g`). Cross-repo audit: 1 more instance in
`src/migrate-plists-to-logs-dir.sh:69-70,111` flagged dormant by
MacBook (one-shot migration already run, no active callers) — not
bundling per `feedback_pr_restraint`, will fix when the file is next
touched.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 pushed a commit that referenced this pull request Apr 17, 2026
…#414)

`sed -i '' ...` is BSD-only syntax. On Macs with Homebrew gnu-sed
ahead of BSD sed in PATH (common dev setup), GNU sed interprets the
empty string `''` as an input filename rather than an in-place
argument, so the rewrite silently fails and `.env` is left with the
stale WEBHOOK_BASE_URL. Next `src/startup.sh` run would restart
services pointed at the wrong ngrok URL.

Fix: tmpfile + mv. Works on both BSD and GNU sed without in-place
gymnastics; no `-i` flag at all.

Same bug class as the PR #412 cold-review findings (`stat -f %m`,
`df -g`). Cross-repo audit: 1 more instance in
`src/migrate-plists-to-logs-dir.sh:69-70,111` flagged dormant by
MacBook (one-shot migration already run, no active callers) — not
bundling per `feedback_pr_restraint`, will fix when the file is next
touched.

Co-authored-by: Chi <wangchi@Chis-Mac-mini.local>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…i#412)

* feat: stage-readiness.sh — single-command pre-flight check for ICLR talk

Mini overnight deliverable sonichi#3 of 4. Runs ~8 checks in <20s and prints
PASS/WARN/FAIL for each. Exits 1 on any FAIL, 2 on any WARN, 0 on all-PASS
so it integrates cleanly with other scripts / CI.

Checks:
  voice-agent          port 9900 listening + log mtime <60s
  bodhi state machine  0× CLOSED→RECONNECTING FATALs in current log (post-sonichi#409)
  conversation-server  port 3100 /health = ok
  ngrok tunnel         curl public URL, expect 200-range
  presenter-mode       sentinel present + future expiry
  Claude quota         >=10% remaining
  disk space           >5 GB free
  memory-sync          last sync <6h

Designed for the 5-10 min pre-talk window. Owner runs it once, sees the
summary, triages the WARNs before going on screen.

Local test: 5 pass, 3 warn, 0 fail. Warnings are all actionable:
- ngrok tunnel unreachable (SSL handshake fails — separate bug, non-blocking
  for remote ICLR talk which uses Zoom audio routing, not phone)
- presenter-mode INACTIVE — expected, run before talk
- memory-sync 9h — run `bash src/sync-memory.sh` before talk

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* fix(stage-readiness): portable stat + df + remove dead ngrok conditional

Three real bugs surfaced by MacBook's cold-review on a Mac-with-Homebrew-
coreutils dev environment (the common setup — MacBook's Apr 26 pre-flight
will run under this shell).

1. `stat -f %m` BSD syntax fails silently on GNU coreutils: GNU emits
   filesystem info to stdout AND exits non-zero. The original
   `BSD || GNU` fallback in `$(...)` concatenated both outputs, then
   `set -u` arithmetic expansion died with "File: unbound variable"
   and silently aborted remaining checks. Fix: new `stat_mtime()` helper
   tries GNU first (works on Linux + coreutils-on-Mac), BSD as fallback,
   explicit `echo 0` as second fallback. Order matters — GNU first is
   correct because GNU's presence is the bug trigger; pure BSD Macs
   without coreutils work cleanly either way.

2. `df -g` is BSD-only. GNU df doesn't support `-g`, so the disk check
   silently printed nothing and was skipped entirely. Fix: POSIX
   `df -k` returns KB universally; convert to GB via arithmetic.

3. Dead `[[ "$probe_url" != */* ]]` conditional on the ngrok probe:
   the pattern matches any URL containing `/` — i.e., every full URL,
   because `https://` has slashes — so the branch always triggers
   regardless of path. The intended "append /health only if no path"
   semantic doesn't work. Fix: just unconditionally append
   `/health` since all Twilio-bound ngrok webhooks need it.

Tested on this Mac: `bash scripts/stage-readiness.sh` → 6 pass, 2 warn
(ngrok SSL handshake + presenter-mode INACTIVE are expected pre-talk),
exit 0.

Credit: MacBook's cold-review-log.md entry on sonichi#412 caught (1) and (2);
(3) I'd already flagged in build_log pass 435 but hadn't fixed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Chi <wangchi@Chis-Mac-mini.local>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
liususan091219 pushed a commit to liususan091219/sutando-fork that referenced this pull request May 28, 2026
…sonichi#414)

`sed -i '' ...` is BSD-only syntax. On Macs with Homebrew gnu-sed
ahead of BSD sed in PATH (common dev setup), GNU sed interprets the
empty string `''` as an input filename rather than an in-place
argument, so the rewrite silently fails and `.env` is left with the
stale WEBHOOK_BASE_URL. Next `src/startup.sh` run would restart
services pointed at the wrong ngrok URL.

Fix: tmpfile + mv. Works on both BSD and GNU sed without in-place
gymnastics; no `-i` flag at all.

Same bug class as the PR sonichi#412 cold-review findings (`stat -f %m`,
`df -g`). Cross-repo audit: 1 more instance in
`src/migrate-plists-to-logs-dir.sh:69-70,111` flagged dormant by
MacBook (one-shot migration already run, no active callers) — not
bundling per `feedback_pr_restraint`, will fix when the file is next
touched.

Co-authored-by: Chi <wangchi@Chis-Mac-mini.local>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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