Skip to content

ci(smoke): replace timeout with a perl alarm so the macOS job can run - #381

Merged
laurigates merged 1 commit into
mainfrom
ci/smoke-perl-timeout-380
Aug 20, 2026
Merged

ci(smoke): replace timeout with a perl alarm so the macOS job can run#381
laurigates merged 1 commit into
mainfrom
ci/smoke-perl-timeout-380

Conversation

@laurigates

Copy link
Copy Markdown
Owner

Closes #380. Picks up @claude's patch from the issue thread — it did the research and chose option B, but its push was rejected because the GitHub App lacks workflows permission on .github/workflows/. This session has it, so I'm landing the work.

One substantive change to that patch — see below; it had a hole that would have made the smoke test green while testing nothing.

The fix

timeout is GNU coreutils. macOS runners ship neither timeout nor gtimeout without brew install coreutils, so Build (macOS) died at command not found before sourcing anything, and has never passed since #374 added it.

Replaced with perl -e 'alarm shift; exec @ARGV; …' 60 … in both jobs, not just macOS: perl is preinstalled on both runner images, the alarm(2) timer survives the exec, and using one mechanism on both platforms means the hang-guard can't drift per-platform. That drops the coreutils assumption rather than papering over it on one side.

The correction to the proposed patch

The bot's form was perl -e 'alarm shift; exec @ARGV' 60 zsh -c '…'. perl's exec() returns only on failure, so with no tail after it the one-liner falls off the end and exits 0 when the target binary is missing:

$ timeout 60s definitely-not-a-real-binary; echo $?
timeout: failed to run command 'definitely-not-a-real-binary': No such file or directory
127

$ perl -e 'alarm shift; exec @ARGV' 60 definitely-not-a-real-binary; echo $?
0          # <- silently green

That is not hypothetical here: the Ubuntu job invokes bare zsh, installed by an earlier setup step. Had that install ever broken, this smoke test would have gone green having sourced nothing — the failure mode .claude/rules calls a lying test. timeout reported 127; the added warn + exit 127 reproduces it.

Verification

Ran the final form directly — this container has no zsh and no macOS runner, so these exercise the wrapper's semantics, which is what changed:

Case Result Want
success 0 0
exit 1 1 1 — real failures must still fail
exit 3 3 exit code passed through, not flattened
hang (sleep 60, alarm 2) 142 in 2s non-zero, at the deadline — not the 6-hour job limit
blocked on stdin (cat) 142 in 2s the actual hazard the guard exists for
missing binary 127 127 — was 0 before the correction

YAML parses and both run: blocks render as intended. actionlint isn't installed in this container, so CI is the first real lint pass — the Linters job covers it.

The issue's acceptance criterion — "a deliberately hanging ~/.zshrc still fails the job rather than running to the 6-hour limit" — is the hang and stdin rows above.

Also

Inline scripts move from double- to single-quoted zsh -c '…', dropping the \"/\$ escaping so zsh expands $ZSH_VERSION itself. Same output, less quoting to get wrong.

Once this merges, #378 gets its base merged in so its Build (macOS) re-runs against a green base — that's its last red check and the only thing standing between it and mergeable.


Generated by Claude Code

`Build (macOS)` has never passed. smoke.yml called `timeout 60s zsh -c …`
unconditionally, but `timeout` is GNU coreutils — macOS runners ship neither
`timeout` nor `gtimeout` without `brew install coreutils`, so the step died
with `command not found` (exit 127) before sourcing anything.

Replaced with `perl -e 'alarm shift; exec @argv; …' 60 …` in BOTH jobs rather
than only the macOS one. perl is preinstalled on both runner images, and the
SIGALRM timer set by alarm(2) survives the exec, so a hang still dies non-zero.
Using one mechanism on both platforms means the hang-guard cannot drift
per-platform, and drops the coreutils assumption instead of papering over it.

The `exit 127` tail is load-bearing, not decoration. perl's exec() returns only
on failure, so the bare `exec @ARGV` form falls off the end of the one-liner and
**exits 0 when the target binary is missing** — the Ubuntu job invokes bare
`zsh`, installed by an earlier step, so a broken install would have produced a
green smoke test that sourced nothing. `timeout` reported 127 there; this
reproduces it.

Verified against the final form: success 0, `exit 1` preserved as 1, `exit 3`
preserved as 3, a hang killed at the alarm (142, at the deadline not the job
limit), and a missing binary 127.

Also switches the inline scripts from double- to single-quoted `zsh -c '…'`,
which drops the `\"`/`\$` escaping and lets zsh expand $ZSH_VERSION itself.

Closes #380
@github-actions github-actions Bot added the ci label Aug 20, 2026
@laurigates
laurigates marked this pull request as ready for review August 20, 2026 05:51
@laurigates
laurigates merged commit 9f2959d into main Aug 20, 2026
9 checks passed
@laurigates
laurigates deleted the ci/smoke-perl-timeout-380 branch August 20, 2026 07:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

ci(smoke): Build (macOS) always fails — timeout is GNU coreutils, absent on macOS runners

2 participants