This document helps coding agents produce high-quality PRs for homebrew-core formula contributions.
- Check for existing PRs for the same formula: open PRs
- Run
brew tap homebrew/coreif not already tapped
- Treat a tap vs
homebrew/coreoverlap as real only when both formulae point to the same upstream project. - For quick screening, compare exact formula name plus
urlanddesc; a name match alone is not enough. - Re-verify any collision whose
urlordescdiffers before proposing removal, rename, or dedupe work. - Known exceptions in this tap:
hellois an intentional overlap and should be kept because it is used to test the tap formula infrastructure.kafkais an intentional overlap because this tap needs Kafka 3.9.zookeeperis an intentional overlap because this tap needs a JDK 21 build; thehomebrew/coreopenjdk-based formula does not work for this use case.cartonis a name-only collision, not a real overlap: this tap packagesswiftwasm/carton, whilehomebrew/corecartonis the Perl CPAN dependency manager.
Preferred method for version bumps:
brew bump-formula-pr --strict <formula> --url=<url> --sha256=<sha256>
# or
brew bump-formula-pr --strict <formula> --tag=<tag> --revision=<revision>
# or
brew bump-formula-pr --strict <formula> --version=<version>This handles URL/checksum updates, commit message, and opens the PR automatically.
If manual editing is needed:
brew edit <formula>
# Update url and sha256 (or tag and revision)
# Leave `bottle do` block unchangedCommit message: foo 1.2.3
For bug fixes or improvements to existing formulae:
brew edit <formula>
# Make changes
# Leave `bottle do` block unchangedCommit message: foo: fix <description> or foo: <description>
Prefer Pathname idioms where possible.
Examples:
session_dir.mkpath
bin.install_symlink libexec.glob("bin/*")- Declare
depends_on "pkgconf" => :buildat the top level by default.pkgconfis a build tool, so keep it OS-agnostic unless the formula has a verified platform-specific build path that never invokespkg-configelsewhere. - Keep platform guards for the libraries that are actually platform-specific, such as Linux-only
gliborlibsecretdependencies.
When a Python formula can reuse a packaged dependency from Homebrew instead of vendoring it as a resource, prefer the shared formula dependency.
- For Pydantic v2 consumers, prefer:
depends_on "pydantic" => :no_linkage
- Do NOT add
depends_on "pydantic-core": there is no standalonepydantic-coreformula in Homebrew;pydantic-coreis provided by thepydanticformula. - Remove vendored
pydantic,pydantic-core, and their helper resources when they are satisfied by the sharedpydanticformula. - If the formula uses
pypi_packages, exclude shared Python formula deps there as well so autobump can manage the remaining vendored resources cleanly. For example:depends_on "certifi" => :no_linkage depends_on "pydantic" => :no_linkage pypi_packages exclude_packages: %w[certifi pydantic]
- Apply the same exclusion pattern to any other shared Python deps moved out of resources, such as
cryptographyorrpds-py. - Prefer source tarballs for Python formula resources. Do not switch resources to wheels just to bypass isolated-build failures; fix the source build inputs or use shared Homebrew dependencies instead. Wheels are acceptable only when upstream has no usable sdist or there is a separately verified packaging reason.
- Do NOT use
uses_from_macos "zlib". - Prefer:
on_linux do depends_on "zlib-ng-compat" end
- Keep this Linux-only unless the formula needs a separate macOS change for other reasons.
Add or increment revision when:
- Fix requires existing bottles to be rebuilt
- Dependencies changed in a way that affects the built package
- The installed binary/library behavior changes
Do NOT add revision for cosmetic changes (comments, style, livecheck fixes).
brew create <url>
# Edit the generated formulaCommit message: foo 1.2.3 (new formula)
- Build source policy: MUST build from source in the formula (e.g.,
go build,cargo install,cmake, etc.).- Do NOT package upstream prebuilt binaries/releases for formula installation.
- If upstream only ships binaries and no buildable source path, raise it for manual review instead of adding the formula.
- Go formulae that need to override Homebrew's default cgo behavior should prefer:
ENV["CGO_ENABLED"] = "1" if OS.linux? && Hardware::CPU.arm?
- Do NOT set
ENV["CGO_ENABLED"] = "1"unconditionally in Go formulae unless the formula has a separately verified requirement outside Linux ARM. - Rust binary formulae MUST use
cargo installwithstd_cargo_args(for examplesystem "cargo", "install", *std_cargo_args). - When the crate root is the current directory, use bare
*std_cargo_argsand do NOT passpath: ".". - Reserve
std_cargo_args(path: "...")for crates that live in a subdirectory. - Do NOT hand-roll standard Rust binary installs with
cargo build+bin.installwhenstd_cargo_argsapplies. - Do NOT manually append
--lockedor--pathwhenstd_cargo_argsis used. - Node.js formulae installed via
npm installwithstd_npm_argsMUST remove pre-built native binaries for non-native architectures after install to passbrew audit. Use the pattern:os = OS.kernel_name.downcase arch = Hardware::CPU.intel? ? "x64" : Hardware::CPU.arch.to_s native = "#{os}_#{arch}" # or "#{os}-#{arch}" depending on module naming prebuild_dir = libexec/"lib/node_modules/<pkg>/node_modules/<native-mod>/build" prebuild_dir.each_child { |dir| rm_r(dir) if dir.basename.to_s != native }
- Common offenders:
koffi(usesos_archunderscored),@napi-rs/*and@swc/*(useos-archhyphenated),node-pty/prebuilds(usesos-archhyphenated). - Bun-based formulae: when the binary has
#!/usr/bin/env bunshebang or requires Bun runtime, usedepends_on "bun"and write a shell wrapper:(bin/"foo").write <<~SH #!/bin/bash exec "#{Formula["bun"].opt_bin}/bun" "#{libexec}/src/main.ts" "$@" SH chmod 0755, bin/"foo"
- TypeScript/npm packages: prefer the standard Homebrew npm install path:
Only add separate dependency-install or build steps when the source archive genuinely lacks required generated files. Keep those steps formula-specific and do not run
system "npm", "install", *std_npm_args bin.install_symlink libexec.glob("bin/*")
npm packmanually. - npm
--min-release-age: packages published < 1 day ago will failstd_npm_argsin local builds. CI will pass once the package ages past the threshold; note this in the PR body when applicable. - Python formulae with Rust-based extensions (tiktoken, cryptography, hf-xet, or any maturin-built package) MUST add
depends_on "rust" => :buildso maturin can compile during pip install. Prefer using shared Homebrew deps (depends_on "cryptography") over vendoring when a formula exists.
- Test block: MUST include at least TWO meaningful assertions — never a version-only test.
- First assertion: ALWAYS try to add a version check, preferring
assert_match version.to_s, shell_output("#{bin}/foo --version")when the binary supports--version. - If
--versiondoes not work, try realistic alternatives such asversion,-V, or-vonly when upstream documents them. - If no version command exists, add a formula-local comment:
# FIXME: Upstream does not expose a version command; replace this with a version assertion when available. - Do NOT use a regex-only version check when
version.to_sworks. - Second assertion: MUST be a real functional check. Do NOT use
--helpas the functional test, and do NOT replace a missing functional test with a help-output assertion. Instead:- Run a non-destructive subcommand with predictable output (e.g.
list,status,info) - For tools that need API keys: invoke with missing key and assert the error message
- For parsers/formatters: feed a small input and assert expected output
- For servers: assert startup error or config validation output
- If no safe deterministic functional check exists, prefer a negative runtime test or leave a precise
FIXMEcomment explaining what upstream capability is missing.
- Run a non-destructive subcommand with predictable output (e.g.
- Matching
--helpalone is NOT a functional test — it only proves the binary loads, not that it works. - Every test should include a concrete binary invocation, preferably the real installed binary under
#{bin}. - For formulae installing multiple binaries, test the primary binary and either run or assert the existence of secondary binaries when appropriate.
- Avoid tests that only check files, completions, or install paths unless the formula is specifically a completion/plugin formula.
- Do NOT write tests that require interactive input, network access, or hang on CI
- Prefer
shell_outputoverOpen3.capture2eunless piping stdin is required - Use
testpathfor temporary files - Do NOT override
HOMEintest dowhen Homebrew already provides the isolated test home. - In particular, do NOT set
ENV["HOME"] = testpathorENV["HOME"] = testpath.to_sintest do. - When a formula genuinely needs a custom
HOMEoutsidetest do(for example during install-time completion generation), scope it to the smallest possible block withwith_envor a command-scoped override instead of mutating globalENV. - Prefer no
HOMEoverride whenever possible. - For TUI formulae, prefer non-interactive stdout/stderr/stdin checks. Use
pipe_output,shell_output(... 2>&1), orOpen3.capture2eonly when stdin/stderr handling is genuinely needed. - For TUI tests, feed minimal stdin such as
q\n, an empty config, a sample input file, or a known invalid input to force deterministic output. - For Kubernetes, Docker, cloud, database, or daemon clients, do NOT require a real cluster, daemon, cloud account, or database. Use invalid config, missing credentials, empty kubeconfig, invalid endpoint, or missing runtime dependency and assert the expected error.
(testpath/"kubeconfig").write("") output = shell_output("KUBECONFIG=#{testpath}/kubeconfig #{bin}/foo status 2>&1", 1) assert_match "expected error", output
- For GUI applications (GTK4, Electron, Qt) that hang on
--versionwithout a display server, still attempt a negative runtime check (e.g. invalid option, missing config). If no deterministic invocation is possible without a display, add:# FIXME: GUI binary requires a display server; replace with a runtime check when headless mode is available.
- First assertion: ALWAYS try to add a version check, preferring
- Completions policy: Add shell completion support when upstream CLI supports it.
- Use Homebrew DSL:
generate_completions_from_executable. - Go/Cobra CLIs: use
shell_parameter_format: :cobrawhen upstream supports the standard Cobracompletion <shell>form; it includes PowerShell by default. - Do NOT combine an explicit
"completion"argument withshell_parameter_format: :cobra; that generatescompletion completion <shell>. - Keep custom Go completion syntax when upstream does not expose the standard Cobra
completion <shell>form, such ascompletion --shell <shell>or non-Cobra frameworks. - Rust CLIs: use
shell_parameter_format: :clapwhen the binary supports Homebrew'sCOMPLETE=<shell>invocation; otherwise keep the explicit"completion","completions", or"--completions"command/flag form used by upstream. - Python CLIs: use
shell_parameter_format: :clickwhen the binary supports Click's_<PROG>_COMPLETE=<shell>_sourceinvocation; use:typerfor Typer CLIs that expose--show-completion <shell>. - Do NOT use completion generation as the formula's functional test.
- In
test do, avoidcompletion,completions, or shell-completion assertions unless the formula is itself a completion/plugin formula with no normal binary behavior. - For completion/plugin-only formulae, test installed completion/plugin files directly and, when possible, shell-load them in a deterministic way.
- Use Homebrew DSL:
- For shell plugins that are not standalone executables (for example Oh My Zsh or Bash plugin repos), package the plugin assets with
pkgshare.installinstead of pretending the repo is a normal binary formula. - Prefer adding a small installer wrapper in
bin/when upstream's install story is "copy these plugin files into a plugin directory". - For Oh My Zsh-style plugins, use a deterministic test with a fake
ZSH_CUSTOMdirectory undertestpath, run the installer wrapper, and assert that the plugin files were copied into the expected plugin directory. - If the plugin needs multiple files (for example
*.plugin.zsh, helper*.zsh, or alib/directory), install and copy the full runtime set; do not package only the entrypoint file. - Add caveats that point users to the installer wrapper or the
pkgsharesource path instead of telling them to clone the repo manually.
-
Prefer installing shared libraries (
.dylib/.so) when upstream supports both shared and static builds. -
Avoid static-only installs unless upstream cannot build shared libraries, or there is a clear technical reason documented in the formula.
-
If upstream lacks
install()rules, manual installation is acceptable, but still prefer installing the shared artifact when available. -
Service block: If the software can run as a daemon, server, agent, listener, worker, or proxy, include a
service doblock.- Place
service doafterdef installand beforetest do. - Verify the service command is stable, foreground-compatible where Homebrew expects it, and does not require interactive setup.
- Do NOT invent a broken service block if upstream lacks a usable daemon mode.
- Prefer
run [opt_bin/"foo", ...], pluskeep_alive trueonly when appropriate. - Add
working_dir,log_path, orerror_log_pathwhen needed by upstream runtime behavior.
- Place
service do run [opt_bin/"foo", "start"] keep_alive true end
- **Livecheck**: Prefer default behavior. Only add a `livecheck` block if automatic detection fails.
- **Head support**: Include when the project has a development branch:
```ruby
head "https://github.com/org/repo.git", branch: "main"
Git repositories MUST specify branch:.
- If local tap intake is blocked only because the current environment falsely rejects an otherwise-valid GitHub
headURL, prefer omittingheadin this tap rather than stalling the formula on local transport validation noise.
- Keep batch packaging work to one formula per PR. In a June 2026 batch,
reqlog,memtui,hulak,diffyml,kite-tui,quokka,epiq,har-viewer, andcrofteach landed as separate PRs throughpr-pull. - Before packaging a candidate, check both
homebrew/coreand this tap for an existing exact package. Skip exact overlaps; for example,herdrandquienalready existed inhomebrew/core. - Do not package upstream prebuilt Homebrew casks or portable release archives as formulae. If upstream has no clean source-build path, leave it for manual review;
pwndbgwas skipped for this reason because upstream's Homebrew distribution used prebuilt casks and the formula source path was not clean enough. - A branch that was already merged through
pr-pullmay be rebased onto latestmainwith the original formula commit skipped as a duplicate. Confirm the formula commit and bottle commit are onmainbefore treating that as resolved. - For Kotlin/Native or C++ interop formulae that hardcode Linux static
libstdc++.apaths, check every Gradle and cinterop definition file for linker options. Thehar-viewerLinux x86 fix required replacing both the app Gradle linker option andftxui_c.def. - If Linux x86 fails against Homebrew GCC's static
libstdc++.awithgetentropyor other missing GLIBC symbols, prefer linking against the brewed sharedlibstdc++.sowith an explicitrpathand runtimedepends_on "gcc". Kotlin/Native invokesld.llddirectly, so use linker flags such as-rpath <dir>, not compiler-driver flags such as-Wl,-rpath,<dir>. - If
ld.lldrejects Homebrewlibstdc++.soreferences withdisallowed by --no-allow-shlib-undefined, pass--allow-shlib-undefineddirectly to the linker.
All checks MUST pass locally before opening a PR:
# Build from source (required)
HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_FROM_API=1 brew install --build-from-source <formula>
# Run tests
brew test <formula>
# Linkage check
brew linkage --test <formula>
# Audit (existing formula)
brew audit --strict <formula>
# Audit (new formula only)
brew audit --new <formula>
# Style check
brew style <formula>- Any formula PR that is not labeled
CI-syntax-onlyMUST go through thepr-pullprocess.- This includes new formulae, version bumps, revision rebuilds, and formula fixes that should produce or refresh bottles.
- After checks pass, wait for the test workflow to add
pr-pull, then let thebrew pr-pullworkflow merge the PR. - Do NOT manually merge these PRs with
gh pr merge, because that bypasses BrewTestBot bottle commits and can leavemainwithout abottle doblock.
- Never force-push
maintomain.git push --force-with-leaseis only for PR head branches that you explicitly verified are notmain.- When updating
main, use a normalgit push origin main. - If local
mainandorigin/maindiverge, rungit pull --rebase origin main, resolve conflicts locally, and then push normally.
- Manual merges are acceptable only for PRs explicitly labeled
CI-syntax-only, meaning CI should run syntax checks only and no bottle-producing build should occur. - If a new formula lands on
mainwithout abottle doblock, open a one-formula follow-up PR that only adds or incrementsrevisionto force a fresh bottle build, and again leave that PR for the bot-managedpr-pullmerge path.
For formula patch PR triage, follow this exact sequence:
- Run brew ops and ensure all pass:
HOMEBREW_NO_AUTO_UPDATE=1 HOMEBREW_NO_INSTALL_FROM_API=1 brew install --build-from-source <formula> brew test <formula> brew linkage --test <formula> brew audit --strict <formula> # or --new for new formulae brew style <formula>
- If any step fails, patch the PR branch with the smallest formula fix and rerun the full brew-ops chain until all steps pass.
- When testing on Linux/macOS
*.upterm.devremote runners, do not runexit,logout, or close the session after brew ops; keep the runner alive for follow-up commands. - When using rerun-backed debug workflows, always attach to the current attempt's job/session rather than an older rerun attempt.
- On a fresh remote runner connection, start with harmless probes such as
pwdanduname -abefore heavier commands. - Do not lead with
set -euo pipefailbefore confirming the runner's working directory and the paths you plan to use actually exist on that runner. - Do not reference workstation-local paths such as
/private/tmp/...or/Users/...on the runner; use heredocs,scp, or create the needed files directly on the runner first.
- Commit on the PR head branch with a short, concise formula patch:
branch="$(gh pr view --json headRefName -q .headRefName)" git switch "$branch" git add Formula/<path>/<formula>.rb git commit -m "<formula>: <short fix>"
- Squash commits while preserving the BrewTestBot-compatible commit subject header:
base="$(gh pr view --json baseRefName -q .baseRefName)" header="$(git log --reverse --format=%s "origin/${base}..HEAD" | head -n1)" # Squash as needed, but keep the final first line equal to "$header" git log -1 --pretty=%s
- Force-update the PR head branch safely:
test "$branch" != main git push --force-with-lease origin "$branch"
- If a global Git config rewrites
https://github.com/pushes togit@github.com:and SSH auth is unavailable, useenv GIT_CONFIG_GLOBAL=/dev/null git push -u https://github.com/<owner>/<repo> "$branch"instead of rewritingorigin.
- If a global Git config rewrites
- Mark the PR with
CI-no-fail-fast:pr="$(gh pr view --json number -q .number)" gh pr edit "$pr" --add-label CI-no-fail-fast
- For any formula PR not labeled
CI-syntax-only, stop after the branch is green and labeled correctly, then leave merge to the bot-managedpr-pullworkflow.- Do NOT use
gh pr mergemanually for formula PRs that should produce bottles. - If the goal is to regenerate missing bottles for a merged formula, open a one-formula
revisionfollow-up PR and again leave merge topr-pull.
- Do NOT use
- If triaging many open PRs, dedupe only version-bump PRs for the same formula by keeping only the latest one.
- Apply this only to PR titles in version-bump format (
<formula> <version>), and skip non-version PRs such asfoo: fix .... - Prefer
brew close-superseded-prs --applyfor this cleanup when it fits; it dry-runs by default and handles both PRs already covered bymainand older open bump PRs superseded by a more recently opened bump.
repo="<owner>/<repo>" gh pr list --repo "$repo" --state open --limit 1000 --json number,title,createdAt > /tmp/open_prs.json jq -r ' # Version-bump titles only: "<formula> <version>" map(select(.title | test("^[^: ]+ [0-9]"))) | sort_by(.createdAt) | group_by(.title | capture("^(?<formula>[^ ]+) ").formula)[] | select(length > 1) | (.[-1].number | tostring) as $keeper | .[0:-1][] | "\(.number) \($keeper)" ' /tmp/open_prs.json > /tmp/superseded_pr_pairs.txt
- Apply this only to PR titles in version-bump format (
- For each older PR, comment + label + close:
repo="<owner>/<repo>" while read -r old_pr keeper_pr; do [ -n "$old_pr" ] || continue printf 'Superseded by #%s\n' "$keeper_pr" > "/tmp/pr-${old_pr}-superseded.md" gh pr comment "$old_pr" --repo "$repo" --body-file "/tmp/pr-${old_pr}-superseded.md" gh pr edit "$old_pr" --repo "$repo" --add-label superseded gh pr close "$old_pr" --repo "$repo" done < /tmp/superseded_pr_pairs.txt
You MUST verify all items before submitting:
- Followed CONTRIBUTING.md
- Commits follow commit style guide
- No existing open PRs for same change
- Built locally with
HOMEBREW_NO_INSTALL_FROM_API=1 brew install --build-from-source - Tests pass with
brew test - Linkage passes with
brew linkage --test - Audit passes with
brew audit --strict(or--newfor new formulae) - Style passes with
brew style
- Version update:
foo 1.2.3 - New formula:
<formula_name> <version> (new formula)- Example:
ls-hpack 2.3.4 (new formula)
- Example:
- Fix/change:
foo: fix <description>orfoo: <description> - First line MUST be 50 characters or less
- Reference issues with
Closes #12345in commit body if applicable
- One formula or cask change per PR — always create a dedicated branch and open a separate PR for each formula/cask, even when working on multiple in the same session
- Never commit formula or cask changes directly to
main; always use a PR branch - Keep diffs minimal and focused
- Provide only essential context in PR description
- For any formula PR not labeled
CI-syntax-only, use thepr-pullmerge path so BrewTestBot adds the bottle commit tomain
- Edit
bottle doblocks (managed by BrewTestBot) - Batch unrelated formula or cask changes into a single PR
- Include large logs or verbose output in PR body
- Add non-Homebrew usage caveats in PR body
- Include unrelated refactors or cleanups
- Manually merge formula PRs that are not labeled
CI-syntax-onlywithgh pr merge
Keep it minimal:
Built and tested locally on [macOS version/Linux].
[One sentence describing the change if not obvious from title.]
When using gh to create/edit PRs or issues:
- Prefer
--body-filewith a heredoc-generated markdown file to preserve newlines. - Avoid passing escaped
\nin quoted--bodystrings. - If inline body text is required, use single quotes around the full body to avoid shell interpolation.
For recurring maintenance work in this tap, prefer the repo-local helpers under cmd/ when they fit:
brew migrate-python <formula>- For tap-local Python migration PRs.
- Works against
chenrui333/tap, refreshes resources withbrew update-python-resources2, pushes the branch, and opens the PR.
brew check <formula>- Shortcut for
brew audit --strict --git --online --fix. - Bare formula names are resolved to
chenrui333/tap/<formula>.
- Shortcut for
brew patch <url>- Fetches a patch URL, computes the SHA-256, and prints a
patch doblock for formula edits.
- Fetches a patch URL, computes the SHA-256, and prints a
brew close-superseded-prs- Dry-runs stale formula bump cleanup by default.
- With
--apply, comments, labelssuperseded, and closes formula bump PRs that are covered bymainor superseded by a more recently opened bump PR for the same formula.
If a helper does not match the job cleanly, fall back to the explicit brew/gh commands in this document instead of forcing the helper into a workflow it was not built for.
- Do not leave tap PR worktrees under Homebrew's tap discovery path, such as
$(brew --repository)/Library/Taps/<owner>/homebrew-tap.<branch>. - Homebrew can treat those branch worktrees as separate taps, which can make
brew updatefail withHOMEBREW_UPDATE_BEFORE_*orHOMEBREW_UPDATE_AFTER_*update-report errors. - For temporary tap PR worktrees, use an out-of-discovery location such as
$(brew --prefix)/var/homebrew-tap-worktrees/<owner>-tap/<date>/..., or move finished worktrees there withgit worktree move. - After batch formula validation, uninstall only the top-level formulae installed for that session, then verify with:
brew update brew tap --verbose brew autoremove --dry-run
- Formula version bumps are owned by autobump-formula.yml. Keep Renovate from opening
Formula/**update PRs; if Renovate proposes a formula update, close it as a duplicate of the BrewTestBot/autobump PR or update .github/renovate.json5. - Do not add
Casks/**to Renovate ignore rules just to mirrorFormula/**. Renovate's current Homebrew manager does not scan casks, and leavingCasks/**visible preserves future native cask support. Cask bumps remain owned by autobump-cask.yml unless the repo intentionally changes policy.
- For workflows that create commits directly on
main, such as update-formula-list.yml, fetchrefs/heads/mainintorefs/remotes/origin/mainbefore deciding whether there is anything to push. IfHEADis already an ancestor ofrefs/remotes/origin/main, exit cleanly. Otherwise rebase ontorefs/remotes/origin/mainand push with a normalgit push origin HEAD:main; never force-pushmain. Treat rebase conflicts as deterministic failures with a clear log, and retry only push rejections caused bymainmoving again. - For platform-aware formula build matrices, inspect changed formula files before trusting coarse triage labels. Mixed platform-only and portable formula changes, or formulae containing both
depends_on :linuxanddepends_on :macos, must use the full matrix. Uselinux-onlyormacos-onlylabels only as a fallback when formula contents cannot be inspected. - For workflows where a dependency job intentionally runs only on pull requests, keep non-PR events explicitly allowed through skipped dependency jobs. Do not require a PR-only job to have
successonpush,schedule, orworkflow_dispatchevents. - When pinning a workflow container to a specific Ubuntu generation, pin the host runner to the matching
ubuntu-<version>label instead ofubuntu-latest. YAML workflow container references should use Renovate-maintained digests; JS-generated dynamic container values may use Homebrew'smaintag only when a nearby comment explains why a floating tag is intentional.
- Reproduce failures locally before debugging
- Read error messages and annotations in "Files changed" tab
- Check complete build log in "Checks" tab if needed
- For Linux failures, use the Homebrew Docker container
- If stuck, comment describing what you've tried
- For tap PR check refreshes, use the repo-local skill at
skills/restart-github-actions-runs/SKILL.md. - Prefer the helper:
skills/restart-github-actions-runs/scripts/restart_pr_actions.sh --repo chenrui333/homebrew-tap <pr> [<pr> ...]
- The helper prefers a safe empty-amend +
git push --force-with-leaseon verified same-repo PR head branches, and falls back togh run rerunwhen the head branch is missing or otherwise not safe to push. - If GitHub HTTPS pushes are being rewritten to SSH by global git config and SSH auth is unavailable, run the helper or any manual PR-branch push under
env GIT_CONFIG_GLOBAL=/dev/nullinstead of changingorigin. - Never edit workflow files just to restart checks.
- Never force-push
main.
- For GitHub package visibility changes, use the repo-local skill at
skills/github-package-visibility/SKILL.md. - Use Playwright MCP with an existing signed-in browser session when available.
- Verify the owner/account context first, change one package before batching, and keep committed examples generic.
- Confirm completion from the filtered
privatepackages view after the run.
If AI assisted with the PR, check the AI checkbox in the PR template and briefly describe:
- How AI was used
- What manual verification was performed