Skip to content

fix: extract Homebrew cask publishing into dedicated publish-cask job - #82

Merged
jflowers merged 1 commit into
unbound-force:mainfrom
yvonnedevlinrh:opsx/fix-homebrew-sha-mismatch
Aug 20, 2026
Merged

fix: extract Homebrew cask publishing into dedicated publish-cask job#82
jflowers merged 1 commit into
unbound-force:mainfrom
yvonnedevlinrh:opsx/fix-homebrew-sha-mismatch

Conversation

@yvonnedevlinrh

Copy link
Copy Markdown
Contributor

Summary

Fixes #81brew install unbound-force/tap/replicator fails on v0.4.0 with a SHA-256 mismatch.

The sign-macos job previously coupled code signing with Homebrew tap publishing in a single job, creating a TOCTOU race condition: re-uploading the signed archive changed its SHA, but a mid-job failure could leave the Homebrew cask with the pre-signing SHA. When signing secrets were absent, the cask was never published at all.

This PR extracts Homebrew cask publishing into a dedicated publish-cask job that:

  • Downloads the final archive (signed or unsigned) from the GitHub Release
  • Computes the SHA-256 from the actual downloaded artifact (not a prior job's shell variable)
  • Validates SHA format (64-character hex) before patching
  • Patches the cask template with awk and verifies the patch with grep
  • Handles both signed (sign-macos success) and unsigned (sign-macos skipped) release paths via always() + explicit result checks
  • Uses minimal permissions (contents: read) — write access only to the tap via HOMEBREW_TAP_TOKEN

How to Test

  1. Structural review: Verify sign-macos no longer contains Homebrew tap logic. Run grep -n "homebrew\|tap\|cask" .github/workflows/release.yml — all matches should be in the publish-cask job section or the header comment.

  2. Conditional logic: The publish-cask if: condition covers all 5 GitHub Actions result states:

    • release=success + sign-macos=success → runs (signed path)
    • release=success + sign-macos=skipped → runs (unsigned path)
    • release=success + sign-macos=failure → does NOT run
    • release=success + sign-macos=cancelled → does NOT run
    • release=failure → does NOT run
  3. End-to-end: Cut a release and verify brew install unbound-force/tap/replicator succeeds.

How to Demo

This is a CI pipeline fix — no user-visible behavior change beyond Homebrew install working correctly again. To demo:

  1. Trigger a release workflow via workflow_dispatch with a test tag (e.g., v0.5.0-rc.1)
  2. Observe the job graph: preflight → release → sign-macos → publish-cask
  3. Verify the publish-cask job downloads the archive, computes SHA, patches the cask, and pushes to the tap
  4. Run brew install unbound-force/tap/replicator and confirm it installs without SHA mismatch errors

Key Files Changed

File Change
.github/workflows/release.yml Extract cask publishing from sign-macos into dedicated publish-cask job (+92/-16)
CHANGELOG.md Add Fixed entry under Unreleased for SHA mismatch bug
openspec/changes/fix-homebrew-sha-mismatch/ OpenSpec change artifacts (proposal, design, specs, tasks)
.uf/dewey/learnings/ Dewey learnings from implementation session

Known Issues

The following findings from the review council were acknowledged but not resolved:

  • LOW: Grep SHA verification is file-wide, not section-scoped (acceptable — only one architecture block exists today)
  • LOW: Token embedded in git clone URL is an existing pattern carried forward, not newly introduced

This PR was generated by /uf.finale (AI-assisted).

The sign-macos job previously coupled code signing with Homebrew tap
publishing, creating a TOCTOU race: re-uploading the signed archive
changed its SHA, but a mid-job failure could leave the cask with the
pre-signing SHA. When signing secrets were absent, the cask was never
published at all.

- Add dedicated publish-cask job that downloads the final archive from
  the GitHub Release and computes SHA from the actual artifact
- Remove Homebrew tap logic from sign-macos (now signing-only)
- Add SHA format validation (64-char hex) and post-patch verification
- Handle both signed (success) and unsigned (skipped) release paths
  via always() + explicit result checks
- Add OpenSpec change artifacts and Dewey learnings

Fixes: unbound-force#81

Assisted-by: OpenCode (claude-opus-4)
Generated with AI assistance — OpenCode (claude-opus-4)
@yvonnedevlinrh
yvonnedevlinrh requested a review from a team as a code owner August 20, 2026 15:22
@yvonnedevlinrh yvonnedevlinrh self-assigned this Aug 20, 2026
@yvonnedevlinrh yvonnedevlinrh added github_actions Pull requests that update GitHub Actions code next-release labels Aug 20, 2026
@yvonnedevlinrh yvonnedevlinrh moved this to Ready for Review 👀 in Unbound Force Planning Aug 20, 2026

@em-redhat em-redhat left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Review: APPROVE

This is a well-structured, well-specced fix that correctly addresses the root cause identified in #81. The TOCTOU race between signing and cask publishing is cleanly eliminated by extracting cask publishing into a dedicated downstream job that computes the SHA from the actual release artifact.

Constitution Alignment

Principle Status Notes
I. Autonomous Collaboration N/A CI-only change, no MCP tools affected
II. Composability First PASS Restores independent installability via Homebrew for both signed and unsigned paths
III. Observable Quality PASS SHA validation regex, grep verification, and explicit error annotations improve observability
IV. Testability PASS Self-test steps validate SHA format and cask patching at release time

Spec Alignment

Implementation faithfully follows all 5 design decisions (D1-D5). All 7 spec scenarios are covered.

Convention Pack Compliance

Rule Status Notes
CI-001 (action pinning) PASS Reusable workflows pinned to SHA
CI-003 (permissions) PASS Top-level permissions: {}, job-level minimal scoping
CI-006 (timeout) PASS timeout-minutes: 10 on publish-cask
CI-009 (set -euo pipefail) PASS All shell steps
CI-014 (secrets via env) PASS Step outputs passed via env: bindings
CI-027 (job dependencies) PASS Proper needs + if guards

Security

  • Token in git clone URL is a pre-existing pattern (not introduced by this PR), acknowledged in Known Issues
  • Step output injection: mitigated by SHA format validation + env: binding pattern

What works well

  1. Empty-string guard on ARM64_SHA catches silent output-passing failures
  2. SHA format validation (^[0-9a-f]{64}$) prevents non-SHA propagation
  3. Stricter grep verification (sha256 "..." vs bare SHA match)
  4. Commit message drops "(signed)" suffix — correct for unsigned path
  5. Clean sign-macos / publish-cask separation
  6. Thorough spec artifacts and learnings

Non-blocking observations

  1. shasum on ubuntu-latest (LOW): Works on GitHub-hosted runners but sha256sum is the more canonical Linux tool. Not worth changing.
  2. Bundled Dewey learnings (INFO): 6 learning files included, some unrelated to this PR (github-config, security-policy, triage). Consider separating in future PRs for cleanliness.

@jflowers
jflowers merged commit 616ce88 into unbound-force:main Aug 20, 2026
1 check passed
@yvonnedevlinrh
yvonnedevlinrh deleted the opsx/fix-homebrew-sha-mismatch branch August 21, 2026 08:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

github_actions Pull requests that update GitHub Actions code next-release

Projects

Status: Ready for Review 👀

Development

Successfully merging this pull request may close these issues.

fix: Homebrew cask SHA mismatch after macOS signing replaces release archive

4 participants