Skip to content

T-824: Homebrew install via automated tap updates#91

Merged
ArjenSchwarz merged 5 commits into
mainfrom
T-824/homebrew-install
Apr 20, 2026
Merged

T-824: Homebrew install via automated tap updates#91
ArjenSchwarz merged 5 commits into
mainfrom
T-824/homebrew-install

Conversation

@ArjenSchwarz

Copy link
Copy Markdown
Owner

Summary

  • Automate Homebrew formula updates on release: the workflow emits .sha256 sidecars for every platform tarball and a new macOS homebrew job renders Formula/rune.rb, validates with brew audit --strict --online plus brew install/brew test, and commits to the ArjenSchwarz/homebrew-rune tap using HOMEBREW_TAP_TOKEN.
  • Idempotent commits and a workflow_dispatch trigger with a tag input let the formula be re-rendered against an existing release without rebuilding binaries; the job is serialised via a concurrency group.
  • README documents the new brew install arjenschwarz/rune/rune path; spec, decision log, and agent notes are updated.

Test plan

  • Cut a test release tag and confirm the homebrew job renders, audits, and pushes Formula/rune.rb to the tap
  • brew install arjenschwarz/rune/rune from the tap succeeds on macOS
  • Re-run the workflow via workflow_dispatch with the same tag and verify the commit is skipped (idempotent)

Add smolspec, decision log, task list, and manual-steps checklist for
publishing rune via Homebrew. The design appends a homebrew job to
release.yml with needs: releases-matrix so it runs only after all four
platform tarballs are uploaded, enables sha256sum: true on the existing
release step so sidecar checksums can be consumed without redownloading
tarballs, validates the rendered formula with brew audit --strict and
brew test on macos-latest, and pushes to ArjenSchwarz/homebrew-rune via
a fine-grained PAT (HOMEBREW_TAP_TOKEN).

Manual prerequisites (create tap repo, generate PAT, configure secret,
cut v1.3.1 to bootstrap) are tracked in specs/homebrew-install/manual-steps.md
to keep tasks.md focused on coding work.

Follow-up tickets T-872 (Rune) and T-873 (Fog) cover macOS binary
signing/notarisation, which is out of scope here.
- Enable sha256sum: true on the go-release-action step so the matrix
  publishes .sha256 sidecars for every platform tarball.
- Add a workflow_dispatch trigger with a `tag` input and gate
  releases-matrix to release events only, allowing manual re-runs of
  the homebrew job against an existing release without rebuilding
  binaries.
- Add a macos-latest homebrew job that downloads the sha256 sidecars,
  parses the digests, renders Formula/rune.rb via heredoc, and
  validates with `brew audit --strict --online`, `brew install`, and
  `brew test rune` before touching the tap.
- Check out ArjenSchwarz/homebrew-rune with HOMEBREW_TAP_TOKEN, commit
  the rendered formula idempotently (no-op when unchanged), and push.
  Serialise runs with a `homebrew-<repo>` concurrency group so
  parallel releases cannot clobber each other.
- Document `brew install arjenschwarz/rune/rune` in README.
- Push explicit branch (HEAD:main) to the tap so tap default-branch
  renames or detached-like checkout state cannot silently redirect the
  formula commit.
- Clean up garbled Blocked-by metadata on tasks.md task 3 left over from
  an earlier rune edit.
- Add specs/homebrew-install/implementation.md with beginner,
  intermediate, and expert explanations plus a completeness assessment
  mapping every MUST/SHOULD requirement to its implementation site.
- batch-command: document phase marker adjustment bug (T-787) and the
  phase detection gap that drops markers on plain remove operations (T-820)
- config-discovery: note that home config tests must isolate HOME (T-812)
- dependencies: document that remove paths bypass RemoveTaskWithDependents
  and the title-output ordering issue (T-801)
- phase-add: new note on AddTaskToPhase dropping extended fields (T-836)
- testing: new note on shared command state leakage (T-857) and current
  known test failures (T-856, T-859)
@claude

claude Bot commented Apr 20, 2026

Copy link
Copy Markdown

Code Review: T-824 — Homebrew install via automated tap updates

Overview

This PR adds an automated Homebrew formula pipeline to the release workflow: enables sha256sum: true on the binary matrix, adds a homebrew job that renders Formula/rune.rb from sidecar files, validates with brew audit --strict --online and brew install/brew test, then commits idempotently to the tap repo. It also adds workflow_dispatch for manual recovery. The design is solid and well-documented.


Issues

Minor (worth fixing before merge)

Heredoc indentation produces a formula with leading whitespace on every line

The cat > Formula/rune.rb <<EOF block is indented 10 spaces inside the YAML run: | block. Because the delimiter is <<EOF (not <<-EOF), bash does not strip any leading whitespace — every line in the rendered formula will begin with 10 spaces. brew audit in CI will catch it if Homebrew actually rejects this, but the formula on disk won't be clean Ruby:

          class Rune < Formula        # ← 10 leading spaces
            desc "CLI for managing…"

Fix: either de-indent the heredoc content to column 0 within the YAML (using a block scalar trick or a separate script), or accept the cosmetic flaw knowing audit validates it. At minimum, worth a comment that the indentation is intentional/benign.

Low (informational)

Actions not pinned to commit SHA

Both uses: actions/checkout@v4 steps are pinned to a mutable tag. If the v4 tag were moved or compromised, the workflow could run arbitrary code with HOMEBREW_TAP_TOKEN in scope. Recommended practice for supply-chain safety is to pin to a full SHA (e.g. actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2). This is a common gap in personal projects, but worth noting given a secret with write access to another repo is in play.

set -u not set on shell steps

GitHub Actions bash steps run with -eo pipefail by default but not -u. If any of the digest variables ($DARWIN_ARM64, etc.) were somehow empty after awk, they would silently write empty strings into the heredoc before the validation loop ran. The validation loop ([ -z "$v" ] || [ ${#v} -ne 64 ]) does catch this, but adding set -u at the top of critical steps (Parse sha256 digests, Render Formula/rune.rb) would fail faster and more clearly. Low risk given the validation is in place.

CHANGELOG entry length

The new Unreleased entry is a dense single-sentence paragraph. Existing entries in this file use brief bullets. Not a functional issue, but inconsistent with the surrounding style.


What's working well

  • Digest validation is thorough: [ -z "$v" ] || [ ${#v} -ne 64 ] correctly rejects malformed sidecars before they can corrupt the formula.
  • Injection-safe tag handling: INPUT_TAG: ${{ github.event.inputs.tag }} in env: then read via $INPUT_TAG prevents expression injection in run: steps — the right pattern.
  • git push origin HEAD:main: Explicit target branch protects against tap default-branch drift (noted in implementation docs as a corrected issue).
  • Idempotent commit: git diff --cached --quiet correctly short-circuits on re-runs, satisfying the no-empty-commits requirement.
  • needs: + if: always(): The gate condition correctly allows releases-matrix result of skipped (on workflow_dispatch) to pass through to the homebrew job.
  • cancel-in-progress: false: Correct choice — back-to-back releases should queue, not race for the tap commit.
  • Decision log: The five decisions are clearly reasoned with alternatives and trade-offs. This is good long-term maintenance documentation.
  • brew audit --strict --online + brew test in CI: Prevents broken formulas from reaching users. The cost (macOS minutes) is free for a public repo.

Summary

The implementation is correct and the design decisions are sound. The heredoc indentation issue is the only thing worth actively addressing — it's invisible to brew audit if Homebrew parses it successfully, but it's a latent cleanliness issue. The supply-chain pin and set -u are low-risk improvements worth considering. Everything else is well-executed.

@ArjenSchwarz

Copy link
Copy Markdown
Owner Author

PR Review Overview - Iteration 1

PR: #91 | Branch: T-824/homebrew-install | Date: 2026-04-20

Valid Issues (fixed)

None.

Invalid/Skipped Issues

Heredoc indentation (Minor — invalid)

  • Reviewer: @claude (PR-level)
  • Claim: cat > Formula/rune.rb <<EOF indented 10 spaces in YAML will produce a formula file with 10 leading spaces per line because <<EOF (vs <<-EOF) does not strip whitespace.
  • Validation: Verified with yaml.safe_load that YAML block scalars (|) strip the auto-detected block indent before bash sees the heredoc body. The heredoc content reaches bash at column 0, so the formula on disk is correctly indented. The bash <<- distinction (which only strips tabs anyway) does not apply here. No change required.

Actions not pinned to commit SHA (Low — deferred)

  • Reviewer: @claude (PR-level)
  • Comment: actions/checkout@v4 should be pinned to a full SHA for supply-chain safety since HOMEBREW_TAP_TOKEN is in scope.
  • Reason: Valid security hygiene point, but Low severity. Tracking as a follow-up rather than blocking this PR.

set -u not set on shell steps (Low — skipped)

  • Reviewer: @claude (PR-level)
  • Comment: Adding set -u would fail-fast on empty digest variables.
  • Reason: The existing validation loop ([ -z "$v" ] || [ ${#v} -ne 64 ]) already catches the case the reviewer is concerned about. Low severity, no change.

CHANGELOG entry length (Low — skipped)

  • Reviewer: @claude (PR-level)
  • Comment: Unreleased entry is a single dense paragraph; surrounding entries use bullets.
  • Reason: Purely stylistic. Low severity, no change.

CI Status

The Test and Build check failed on cmd.TestRunCompleteDryRun, cmd.TestResolveFilename, cmd.TestRenumberPreservesAllPhaseMarkers, and an internal/task build failure. These failures are pre-existing on main (verified: recent main runs are also red) and are tracked separately as T-856 (internal/task/phase_test.go stale call), T-857 (TestRunCompleteDryRun shared command state), and T-859 (TestRenumberPreservesAllPhaseMarkers phase-marker mapping). This PR only touches workflow YAML, README, agent notes, and specs — no Go code — so it cannot have introduced these. Out of scope for this PR.

@claude

claude Bot commented Apr 20, 2026

Copy link
Copy Markdown

Claude finished @ArjenSchwarz's task in 2m 29s —— View job


PR Review Overview — Iteration 2

  • Gather context and read Iteration 1 analysis
  • Read changed files for second-pass review
  • Assess Iteration 1 dispositions for correctness
  • Provide Iteration 2 verdict

Iteration 1 Disposition Verification

All four dispositions from Iteration 1 are confirmed correct:

Heredoc indentation — verified empirically. Python's yaml.safe_load on the actual block scalar proves YAML strips the 10-space indent before bash processes the heredoc body. The formula reaches bash at column 0; <<-EOF vs <<EOF is irrelevant. No change needed.

SHA pinning, set -u, CHANGELOG style — Low/stylistic, deferred or skipped correctly.


Iteration 2: New Finding

Minor — Missing license field in formula

The rendered Formula/rune.rb (release.yml lines 109–143) does not include a license field. The repository is MIT-licensed (confirmed from LICENSE). brew audit --strict checks for license in formulas; under --strict, missing license is treated as an error rather than a warning, which would fail the brew audit step in CI before the formula ever reaches the tap.

Fix: add license "MIT" between version and on_macos do:

version "${VERSION}"
license "MIT"

on_macos do

Fix this →

Caveat: if you've already run brew audit --strict against a test formula and it passed without license, then Homebrew's tap-formula audit is more lenient than core and this can be skipped. Otherwise it's worth adding before cutting the first release.


Overall Verdict

The implementation is correct and ready to merge pending the license field question above. All other design decisions (injection-safe tag handling, idempotent commit, if: always() gate, cancel-in-progress: false, digest validation loop, git push origin HEAD:main) are sound and well-executed. CI failures are pre-existing and confirmed out of scope for this PR.
| Branch

@ArjenSchwarz ArjenSchwarz merged commit e67f33e into main Apr 20, 2026
11 of 12 checks passed
@ArjenSchwarz ArjenSchwarz deleted the T-824/homebrew-install branch April 20, 2026 05:13
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