Skip to content

feat(binary): brew tap formula + release automation hook - #624

Merged
KnockOutEZ merged 2 commits into
studio-handoff-corefrom
sd-507-feat-binary-brew-tap-formula-rel
Sep 8, 2026
Merged

feat(binary): brew tap formula + release automation hook#624
KnockOutEZ merged 2 commits into
studio-handoff-corefrom
sd-507-feat-binary-brew-tap-formula-rel

Conversation

@KnockOutEZ

Copy link
Copy Markdown
Owner

Closes wigolo-studio-run#507 (BIN-6, single-binary mini-spec §5).

brew install KnockOutEZ/wigolo/wigolo now works, against a real published prerelease, with the tap's version bump automated.

What shipped

The tap — KnockOutEZ/homebrew-wigolo (external by construction, named in this issue's lane-extra: rule):

  • scripts/formula.sh <tag> regenerates the whole formula from that release's own SHA256SUMS. Everything that differs between releases is four url/sha256 pairs and a version; a positional sed over a Ruby file can half-apply that, generating from the one authoritative input cannot. The version is read out of an asset name, so the formula can never claim a version the assets it points at do not carry.
  • .github/workflows/bump.yml runs that script and opens the version-bump PR, on repository_dispatch, an hourly schedule, and workflow_dispatch.

Core — .github/workflows/binary-release.yml gains a tap job that pokes the tap once a release has passed the consumer smoke.

Why the PR is opened by the tap and not by the release workflow (A-507-1)

Spec §5 says "the release workflow opens the version-bump commit against the tap". A core job can only write to the tap with a cross-repo token, and a token expires, gets revoked, and is missing exactly when a release is cut — the literal design makes the bump's availability equal to the credential's. Inverted: the tap opens the PR with its own GITHUB_TOKEN off the public release feed, its hourly schedule is the guarantee, and the tap job here is the accelerator. With HOMEBREW_TAP_TOKEN the PR appears in seconds; without it the identical PR appears within the hour; and its absence exits 0 with a notice rather than failing a release whose assets are already published.

The job needs smoke, because the bump asks the tap to point users at these artifacts and the consumer smoke is the evidence they are installable. The poke deliberately cannot name a tag: all three triggers resolve one rule — newest release carrying binary assets, stable preferred over prerelease — because a scratch tag on the binary-only channel is a real published prerelease, and a payload the tap obeyed would let one become the formula ahead of a stable release. Recorded in DECISIONS-AUTO.md with its reversal condition.

Acceptance

1. brew install yields a working wigolo --version + MCP handshake on the host. Against binary-v0.2.1-sd507.1, a real published prerelease (5-target build + platform-native verify + publish + 3-OS consumer smoke, all green — run 34192182228). The battery is tests/integration/binary/consumer-smoke.sh, run under clean-path.sh so no Node is on PATH:

$ brew install KnockOutEZ/wigolo/wigolo
🍺  /opt/homebrew/Cellar/wigolo/0.2.1: 8,940 files, 403.6MB, built in 6 seconds
$ wigolo --version
wigolo 0.2.1

node --version failed under PATH=/usr/bin:/bin:/usr/sbin:/sbin, as required.
== consumer smoke: /opt/homebrew/bin/wigolo (expecting 0.2.1) ==
  ok   G5 — --version says 0.2.1
  ok   G1 — runs from a relocated copy under a path with a space
  ok   RUN — MCP handshake answered, stdout byte-clean, serverInfo says 0.2.1
  ok   OPS — fetch https://example.com returned 6274 bytes
  ok   OPS — cache stats answered from the artifact's own database
  n/a  G2 — no unprivileged network-namespace equivalent on Darwin; the linux leg carries this arm
consumer smoke green (0 deferred).

2. Checksums match SHA256SUMS; brew audit --strict clean. The checksums are not compared to SHA256SUMS, they are generated from it by scripts/formula.sh. brew audit --strict exits 0 with zero warnings and zero waivers — two orderings it flagged are fixed rather than waived (livecheck before the platform blocks, skip_clean before install). brew test wigolo green.

3. The automation step is exercised on a scratch tag. End to end and live: scratch tag → release workflow → tap workflow → tap PRs #2, #3 and #4, each opened by the automation and merged. Two defects in the automation only a live run could show, both fixed here: Formula/ does not exist on the first bump into an empty tap (git carries no empty directories), and an untracked formula is invisible to git diff --quiet — so the run that mattered most reported "nothing to bump". A third, gh pr view answering with the merged PR on a reused branch name and skipping creation, is now an explicit --state open check.

The named-blocker + follow-up pair also exists, for the accelerator half only: no session can mint the credential (gh auth token yields a gho_ OAuth token with broad repo scope and no workflow scope; a minimum-scope fine-grained PAT or App installation token needs interactive browser auth), and wigolo-studio-run#522 is filed ready in Q-lane for it. The missing token costs latency, not the bump.

A defect in #509's shipped smoke, found only by standing where brew stands

consumer-smoke.sh derived the installed tree from dirname of the entry point, but both acquisition paths hand it a symlink: install.sh links ~/.local/bin/wigolo at an absolute path, brew links <prefix>/bin/wigolo into the keg relatively. So G1 copied ~/.local or the whole Homebrew prefix. On the brew leg that failed outright, which is how it surfaced; on the install.sh leg the arm had been passing vacuously, because the copied absolute symlink pointed back at the untouched original and the arm re-ran the binary from its original location and called that relocation.

Fixed by resolving the entry symlink first, and proven with a base-vs-tip differential on a fixture whose relocation genuinely breaks (a stand-in that resolves its own realpath and refuses to run anywhere but where it was built):

=== TIP (fixed) ===
  FAIL G1 (relocatable) broke: the relocated copy would not start: baked path lost: …/a moved/place/wigolo/bin
=== BASE (before the fix) ===
  ok   G1 — runs from a relocated copy under a path with a space

Suites

npm test 12080 pass / 1 fail · npx tsc --noEmit clean · npm run typecheck:studio clean · npm run gate:studio clean (debt holds at baseline 341).

The one red is tests/unit/telemetry/queue.test.ts:211expected 111.61 to be less than 100. That is the #506 wall-clock-budget flake class, already recorded in known-issues.md, measured red on Windows at the same line and the same 100 ms budget at 479.62. Green in isolation (19/19), and this branch's diff is one workflow file plus a shell script, so nothing in it can reach telemetry.

Notes

  • The scratch tag and its release are kept, not cleaned up: it is the only release carrying binary assets, and the formula points at it. The tap tracks stable in preference as soon as a v* release ships binaries.
  • No cask, no bottles, no npm changes (§10). No core src/ change.

…s a tree

Both acquisition paths hand consumer-smoke.sh a symlink rather than the executable:
install.sh links ~/.local/bin/wigolo at an absolute path inside ~/.wigolo/dist, and brew
links <prefix>/bin/wigolo at a relative path inside the keg. Deriving the installed tree
from dirname of the link gave ~/.local or the Homebrew prefix, so the G1 arm copied the
wrong tree — and on the install.sh leg the copied absolute symlink pointed back at the
untouched original, which made the arm re-run the binary from its original location and
call that relocation. On the brew leg the same mistake copied the Homebrew prefix and the
arm failed outright, which is how it surfaced.
…sumer smoke

The formula lives in KnockOutEZ/homebrew-wigolo and github.token is scoped to this
repository, so the PR is opened by the tap's own workflow with the tap's own GITHUB_TOKEN,
from a public release feed it can read without credentials. That workflow also runs hourly,
so this job is the accelerator and the schedule is the guarantee: with HOMEBREW_TAP_TOKEN
present the bump PR appears in seconds, without it the identical PR appears within the
hour, and a missing token can never fail a release whose assets are already published.

It needs the consumer smoke because the bump asks the tap to point users at these
artifacts, and it carries no instruction because a scratch tag on the binary-only channel
is a real published prerelease — the tap re-resolves its own target, preferring stable.
@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Advanced

Run ID: 4c9d59e3-8e4f-4c65-b4e4-b04103dfb6b9

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@KnockOutEZ
KnockOutEZ merged commit 73f3f79 into studio-handoff-core Sep 8, 2026
20 checks passed
@KnockOutEZ
KnockOutEZ deleted the sd-507-feat-binary-brew-tap-formula-rel branch September 8, 2026 06:37
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