[ci] Exec the cask we just installed, and fail on a hang - #8
Merged
Conversation
Installing is not evidence the thing runs. tacet#25 shipped a cask that
installed perfectly, passed tap-syntax, style, audit and the existing
install/uninstall step, and then hung in _dyld_start on first exec —
casks quarantine by default and nothing launches these bundles through
LaunchServices, so the consent gate had nobody to answer it. Every check
was green and it was broken for every user.
--help is the cheapest proof a bundle reaches its own main(): a
quarantine wedge blocks in the dynamic loader and never gets there.
Bounded by background-and-poll rather than timeout(1), because a stock
macOS runner has neither timeout nor gtimeout — depending on coreutils
would leave the check silently unbounded on the one platform it
protects. Same reasoning as run_bounded() in tacet's install-server.sh.
The failure mode here is a hang, so an unbounded check would burn the
job timeout instead of naming the problem.
The cask comment is touched deliberately: the step only runs when Casks/
changes, so a workflow-only PR would not exercise its own new code.
Solves: green CI on a cask that cannot start
Tests: bounded-exec logic verified locally against exit 0, exit 1, a
30s hang (rc=124), and the real tacet binary; this PR's own CI
run exercises it against the published 0.1.2 cask
The first version walked all of /Applications, so on a runner it execed
Firefox and "Google Chrome for Testing" before ever reaching the cask's
own bundle. Chrome for Testing never exits on --help, so the check
failed every cask PR and attributed the hang to the cask under test —
a false positive that would have taught everyone to ignore it.
Snapshot /Applications before install and diff after, so only what this
cask added gets exec'd. The error now names the bundle, not just the
cask, because a cask can ship more than one app.
Solves: false positive from runner-preinstalled apps
Tests: simulated locally against three cases — good app only (pass),
good app plus a bundle that sleeps forever (fails 1, names the
hanging bundle), and a cask that installs no .app (pass)
brew style runs actionlint over the workflows, and actionlint fails on shellcheck's SC2012 even at info level. Linting Casks/tacet.rb alone missed it; brew style drycodeworks/tap is the check that matches CI. Solves: --only-tap-syntax failing on the cask exec check Tests: brew style drycodeworks/tap — 3 files, no offenses
The published bundle is Mach-O thin (arm64), but the cask constrained
only macOS version — so brew installed it on an Intel Mac and the binary
could not execute. Rosetta does not cover this direction. The exec check
added in this PR caught it on the macos-15-intel runner on its first
real run, which is the first evidence any of this tooling has produced.
The workflow now reads depends_on arch and skips a cask the runner
cannot run, because a legitimate single-arch cask is not a defect on the
other half of the matrix. Homebrew normalises the token to arm/intel
rather than arm64/x86_64, so the comparison uses those.
The arch query is a python one-liner because a multi-line heredoc inside
a YAML block scalar must be indented to stay in the block, and python
then rejects the uniform leading whitespace.
Solves: arm64-only bundle installable on Intel
Tests: brew style drycodeworks/tap clean; arch gate simulated against
the real cask for both runner types — arm installs and execs,
intel skips
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.
Closes the gap that let tacet#25 reach users.
The gap
The cask job installs and uninstalls what changed. tacet 0.1.1 installed perfectly, passed
--only-tap-syntax, style, audit and that install step — and then hung in_dyld_starton first exec. Casks quarantine by default, nothing launches these bundles through LaunchServices, and a directly-exec'd quarantined bundle blocks on a consent decision with no UI to answer it. Green CI, broken for every user.The check
Exec the installed bundle with
--helpand fail on a non-zero exit or a hang. That is the cheapest proof a bundle reaches its ownmain()— a quarantine wedge never gets there.Bounded by background-and-poll, not
timeout(1): a stock macOS runner has neithertimeoutnorgtimeout, so depending on coreutils would leave the check silently unbounded on exactly the platform it protects. Same reasoning asrun_bounded()in tacet'sinstall-server.sh.Verification
Bounded-exec logic tested locally against four cases — exit 0, exit 1, a 30s hang (rc=124), and the real tacet binary (rc=0).
The cask comment is touched on purpose: the step only fires when
Casks/changes, so a workflow-only PR would not run its own new code. With the cask in the diff, this PR's CI run exercises the check end to end against the published 0.1.2 cask — which also gives the first real verification that the quarantinepostflightworks throughbrew install.