Skip to content

fix(release): emit postflight_steps in the Homebrew cask - #11

Merged
leinardi merged 3 commits into
mainfrom
fix/cask-postflight-steps
Sep 11, 2026
Merged

leinardi merged 3 commits into
mainfrom
fix/cask-postflight-steps

Conversation

@leinardi

Copy link
Copy Markdown
Owner

Summary

Installing the cask from the tap printed a deprecation warning on every brew command that touched it:

Warning: Calling `postflight` is deprecated! Use `postflight_steps` instead.
Please report this issue to the leinardi/homebrew-tap tap (not Homebrew/* repositories), or even better, submit a PR to fix it:
  /opt/homebrew/Library/Taps/leinardi/homebrew-tap/Casks/monmux.rb:25

Cause

goreleaser renders homebrew_casks.hooks.post.install as a Ruby postflight do … end block. Homebrew has replaced flight blocks with the declarative *_steps stanzas. Official taps already reject the old blocks (Homebrew/brew#23366); third-party taps get this warning until a later removal. Once that removal lands, the quarantine strip would silently stop running and Gatekeeper would refuse the unsigned binary. The tap file is generated, so the fix has to be in .goreleaser.yaml.

Fix

goreleaser v2.18.1 has no option that emits the new stanza. goreleaser/goreleaser#6873 adds hooks.post.install_steps, but it is still open. Until it ships, the block goes through custom_block:

postflight_steps do
  on_macos do
    run "/usr/bin/xattr",
        args:         ["-dr", "com.apple.quarantine", "{{staged_path}}/monmux"],
        must_succeed: false
  end
end
  • goreleaser templates the whole cask, so the YAML escapes Homebrew's {{staged_path}} token.
  • must_succeed: false is deliberate. The old system_command never raised either, and xattr -d exits 1 when the attribute is absent, which would fail the install for no reason. stderr stays visible, so a real failure still shows.
  • custom_block puts the stanza before the artifact stanzas. brew style flags that order; installing ignores it, and the tap runs no style check. Moving to install_steps once goreleaser releases it fixes the order. The YAML comment says so.

Docs

  • docs/release.md and docs/security.md name the new stanza.
  • docs/security.md no longer compares the strip to brew install --cask --no-quarantine, because Homebrew removed that flag.
  • README.md adds one sentence on why monmux info masks Handle: on macOS but not on Linux. The macOS handle is the display UUID, redacted like a serial; the Linux handle is only the connector name. That difference is intended, and now written down.

Testing

  • goreleaser release --snapshot --clean --skip=publish,sign with the pinned v2.18.1: the generated dist/homebrew/Casks/monmux.rb contains the block above with a literal {{staged_path}}, and no postflight do or preflight do.
  • By hand on an Apple Silicon Mac, against the v0.6.0 release archive, with the tap's cask temporarily replaced:
    • Control, a cask with no strip: the binary keeps com.apple.quarantine, and running it brings up Gatekeeper's "Apple could not verify" dialog.
    • The rendered block: install and reinstall print no deprecation warning, xattr -l shows no quarantine attribute, and monmux version (exit 0) and monmux info run.
  • make check passes.

Pull request checklist

  • I am targeting the main branch
  • I have rebased this branch on top of the destination branch
  • I have executed make check locally before creating the commit and it has run successfully
  • I have performed a self-review of my own code
  • There are no WIP commits in this PR

Monitor writes

None. internal/catalog/models.yaml is not in the diff, and no code changed. The only monmux commands run on hardware were version and info, both read-only, run by hand on the Mac.

  • If this changes internal/catalog/models.yaml, the evidence is in the entry, and the Summary above says which model
    and which input it enables — not applicable
  • No hardware run in this pull request was performed by an AI agent — AGENTS.md

Type of changes

  • 🐛 Bug fix
  • ✨ New feature
  • 🖥️ Catalog (a model or an input recorded, enabled or corrected)
  • 🔧 Refactoring
  • 📜 Docs
  • 🧰 CI / tooling / infra
  • Other (describe in Summary)

goreleaser renders homebrew_casks.hooks.post.install as a Ruby `postflight`
block, which Homebrew has deprecated in favour of the declarative
`postflight_steps`. Every brew command touching the cask printed

  Warning: Calling `postflight` is deprecated! Use `postflight_steps` instead.

and told the user to report it to the tap. Official taps already reject flight
blocks; third-party taps keep them only until a later removal, and after that
the quarantine strip would silently stop running and Gatekeeper would refuse
the unsigned binary.

goreleaser v2.18.1 cannot emit the new stanza - goreleaser/goreleaser#6873
adds hooks.post.install_steps and is unreleased - so the block is written
through custom_block, as a `run` step invoking /usr/bin/xattr. Homebrew's
{{staged_path}} token is escaped, because goreleaser templates the whole cask.

must_succeed: false is kept on purpose. The old system_command never raised
either, and `xattr -d` exits 1 when the attribute is absent, which would fail
the install over nothing. stderr stays visible. custom_block puts the stanza
ahead of the artifacts, which `brew style` flags and installing ignores; the
move to install_steps fixes that.

docs/release.md and docs/security.md name the new stanza, and security.md no
longer compares the strip to `brew install --cask --no-quarantine`, a flag
Homebrew has removed.

Verified on an Apple Silicon Mac against v0.6.0. A cask with no strip installs
a quarantined binary that Gatekeeper refuses. The cask as goreleaser v2.18.1
renders it installs and reinstalls with no deprecation warning, leaves no
com.apple.quarantine attribute, and monmux version and monmux info run.
The macOS handle is the display UUID, which identifies a physical unit and is
redacted like a serial. The Linux handle is the connector name, which is not
private. The difference shows side by side in `monmux info`, so the README's
redaction paragraph now says why.
…around

The custom_block exists only because goreleaser cannot emit postflight_steps
yet. The comment now names the issue and the pull request that fixes it, and
says what to do once a release carries hooks.post.install_steps: bump
GORELEASER_VERSION in both workflows, move the block there, and drop the
stanza-order note.
@leinardi
leinardi merged commit 30b1b62 into main Sep 11, 2026
12 checks passed
@leinardi
leinardi deleted the fix/cask-postflight-steps branch September 11, 2026 12:32
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