agentleFS
Data-WiseData-Wise/craft/skills/distribution/homebrew-workflow-expert/SKILL.md

homebrew-workflow-expert

Skill4 starsChanged 13 days agoMITView on GitHub

This skill should be used when the user asks to "create Homebrew release workflow", "automate formula updates with GitHub Actions", "configure tap CI", or needs GitHub Actions workflows for automated Homebrew formula updates and releases.

  • Pipes a download into a shell
  • Reads credentials
  • Installs packages

The file

We split this skill into the rules it gives an agent

2 rules. About 0 are common, 2 are distinctive, and 0 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈2

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 0

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Distinctive:

    Only if you cannot use a GitHub App.

    line 274
  2. Distinctive:

    No branch protection requiring reviews

    line 431

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

Homebrew Workflow Expert

Deep expertise in GitHub Actions workflows for automating Homebrew formula updates, releases, and tap management.

Surface scope: Homebrew installs plugins to the Claude Code CLI surface (~/.claude/). Claude Desktop (DXT/MCPB extensions — a different, MCP-server format) and Cowork are separate surfaces; see dist-extras and commands/dist/surfaces.md for the full model.

Reusable Workflow Pattern

The recommended approach is a centralized reusable workflow in your homebrew-tap repository.

Tap Repository Structure

homebrew-tap/
├── .github/
│   └── workflows/
│       └── update-formula.yml    # Reusable workflow
├── Formula/
│   ├── myapp.rb
│   └── othertool.rb
└── README.md

Reusable Workflow (update-formula.yml)

name: Update Formula

on:
  workflow_call:
    inputs:
      formula_name:
        required: true
        type: string
        description: 'Name of the formula to update (e.g., myapp)'
      version:
        required: true
        type: string
        description: 'New version (e.g., 1.2.3)'
      sha256:
        required: true
        type: string
        description: 'SHA256 hash of the release tarball'
      source_type:
        required: false
        type: string
        default: 'github'
        description: 'Source type: github or pypi'
      source_repo:
        required: false
        type: string
        description: 'Source repository (default: github.repository)'
      auto_merge:
        required: false
        type: boolean
        default: false
        description: 'Auto-merge the PR after creation'
    secrets:
      tap_token:
        required: true
        description: 'PAT with access to tap repository'

jobs:
  update:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout tap repository
        uses: actions/checkout@v4
        with:
          repository: YOUR-ORG/homebrew-tap
          token: ${{ secrets.tap_token }}

      - name: Update formula
        run: |
          FORMULA_FILE="Formula/${{ inputs.formula_name }}.rb"

          if [ ! -f "$FORMULA_FILE" ]; then
            echo "Error: Formula $FORMULA_FILE not found"
            exit 1
          fi

          # Update version in URL
          if [ "${{ inputs.source_type }}" = "pypi" ]; then
            # PyPI URL pattern
            sed -i "s|url \"https://files.pythonhosted.org/packages/.*/.*-[0-9.]*\.tar\.gz\"|url \"https://files.pythonhosted.org/packages/source/${FIRST_CHAR}/${{ inputs.formula_name }}/${{ inputs.formula_name }}-${{ inputs.version }}.tar.gz\"|" "$FORMULA_FILE"
          else
            # GitHub URL pattern
            SOURCE_REPO="${{ inputs.source_repo || github.repository }}"
            sed -i "s|url \"https://github.com/.*/archive/refs/tags/v[0-9.]*\.tar\.gz\"|url \"https://github.com/${SOURCE_REPO}/archive/refs/tags/v${{ inputs.version }}.tar.gz\"|" "$FORMULA_FILE"
          fi

          # Update SHA256
          sed -i "s|sha256 \"[a-f0-9]*\"|sha256 \"${{ inputs.sha256 }}\"|" "$FORMULA_FILE"

          echo "Updated $FORMULA_FILE to version ${{ inputs.version }}"
          cat "$FORMULA_FILE"

      - name: Create Pull Request
        id: create-pr
        uses: peter-evans/create-pull-request@v5
        with:
          token: ${{ secrets.tap_token }}
          commit-message: "Update ${{ inputs.formula_name }} to ${{ inputs.version }}"
          title: "Update ${{ inputs.formula_name }} to ${{ inputs.version }}"
          body: |
            Automated formula update from release workflow.

            **Changes:**
            - Version: ${{ inputs.version }}
            - SHA256: ${{ inputs.sha256 }}
            - Source: ${{ inputs.source_type }}
          branch: update-${{ inputs.formula_name }}-${{ inputs.version }}
          base: main
          delete-branch: true

      - name: Auto-merge PR
        if: inputs.auto_merge && steps.create-pr.outputs.pull-request-number
        env:
          GH_TOKEN: ${{ secrets.tap_token }}
        run: |
          gh pr merge ${{ steps.create-pr.outputs.pull-request-number }} \
            --repo YOUR-ORG/homebrew-tap \
            --merge \
            --delete-branch

Caller Workflow Pattern

In each project that uses Homebrew:

GitHub Source (Default)

name: Homebrew Release

on:
  release:
    types: [published]
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to release (e.g., 1.2.3)'
        required: true
      auto_merge:
        description: 'Auto-merge the PR'
        type: boolean
        default: true

jobs:
  prepare:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.version }}
      sha256: ${{ steps.sha256.outputs.sha256 }}
    steps:
      - name: Get version
        id: version
        run: |
          if [ "${{ github.event_name }}" = "release" ]; then
            VERSION="${{ github.event.release.tag_name }}"
            VERSION="${VERSION#v}"
          else
            VERSION="${{ github.event.inputs.version }}"
          fi
          echo "version=$VERSION" >> $GITHUB_OUTPUT
          echo "Version: $VERSION"

      - name: Calculate SHA256
        id: sha256
        run: |
          TARBALL_URL="https://github.com/${{ github.repository }}/archive/refs/tags/v${{ steps.version.outputs.version }}.tar.gz"
          SHA256=$(curl -sL "$TARBALL_URL" | shasum -a 256 | cut -d' ' -f1)
          echo "sha256=$SHA256" >> $GITHUB_OUTPUT
          echo "SHA256: $SHA256"

  update-homebrew:
    needs: prepare
    uses: YOUR-ORG/homebrew-tap/.github/workflows/update-formula.yml@main
    with:
      formula_name: myapp
      version: ${{ needs.prepare.outputs.version }}
      sha256: ${{ needs.prepare.outputs.sha256 }}
      source_type: github
      auto_merge: ${{ github.event.inputs.auto_merge == 'true' || github.event_name == 'release' }}
    secrets:
      tap_token: ${{ secrets.HOMEBREW_TAP_GITHUB_TOKEN }}

PyPI Source

name: Homebrew Release (PyPI)

on:
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to release (matches PyPI)'
        required: true

jobs:
  prepare:
    runs-on: ubuntu-latest
    outputs:
      version: ${{ steps.version.outputs.version }}
      sha256: ${{ steps.sha256.outputs.sha256 }}
    steps:
      - name: Get version
        id: version
        run: echo "version=${{ github.event.inputs.version }}" >> $GITHUB_OUTPUT

      - name: Get PyPI SHA256
        id: sha256
        run: |
          PACKAGE_NAME="myapp"
          VERSION="${{ steps.version.outputs.version }}"

          # Fetch package info from PyPI API
          PYPI_JSON=$(curl -s "https://pypi.org/pypi/${PACKAGE_NAME}/${VERSION}/json")

          # Extract SHA256 for source distribution (.tar.gz)
          SHA256=$(echo "$PYPI_JSON" | jq -r '.urls[] | select(.packagetype == "sdist") | .digests.sha256')

          echo "sha256=$SHA256" >> $GITHUB_OUTPUT
          echo "SHA256: $SHA256"

  update-homebrew:
    needs: prepare
    uses: YOUR-ORG/homebrew-tap/.github/workflows/update-formula.yml@main
    with:
      formula_name: myapp
      version: ${{ needs.prepare.outputs.version }}
      sha256: ${{ needs.prepare.outputs.sha256 }}
      source_type: pypi
      auto_merge: true
    secrets:
      # Preferred: GitHub App token (short-lived, minted per run). PAT is fallback.
      app_id: ${{ secrets.APP_ID }}
      app_private_key: ${{ secrets.APP_PRIVATE_KEY }}

Auth Setup

Cross-repo pushes to the tap are authenticated primarily by a GitHub App token (short-lived, scoped, minted per run by actions/create-github-app-token); a fine-grained PAT is a fallback for setups without the App. The tap's reusable update-formula.yml accepts both (secrets: app_id + app_private_key, or tap_token), preferring the App token when present.

Preferred: GitHub App (APP_ID + APP_PRIVATE_KEY)

Install a GitHub App (e.g. "Data-Wise Homebrew Automation") on the tap repo with Contents: read/write + Pull requests: read/write, then store its credentials as secrets in each source repo:

gh secret set APP_ID --repo YOUR-ORG/myapp            # the App's numeric ID
gh secret set APP_PRIVATE_KEY --repo YOUR-ORG/myapp   # the App's .pem private key

The workflow mints a token per run — nothing to rotate, no expiry to track.

Fallback: Fine-Grained PAT

Only if you cannot use a GitHub App. Create a fine-grained Personal Access Token:

SettingValue
Namehomebrew-tap-updater
Expiration90 days (set reminder to rotate)
Repository accessOnly select repositories
Selected repositoriesYOUR-ORG/homebrew-tap
PermissionsContents: read/write · Pull requests: read/write
gh secret set HOMEBREW_TAP_GITHUB_TOKEN --repo YOUR-ORG/myapp
gh secret list --repo YOUR-ORG/myapp   # verify

Rotate before expiry: create new token → update in all repos → delete old → set a calendar reminder. (The App path avoids all of this.)

Auto-Merge Strategies

Strategy 1: Immediate Auto-Merge

auto_merge: true  # Always auto-merge

Best for:

  • Personal taps
  • Trusted automated releases
  • CI-validated releases

Strategy 2: Release-Only Auto-Merge

auto_merge: ${{ github.event_name == 'release' }}

Best for:

  • Manual workflow dispatches need review
  • Releases are pre-validated

Strategy 3: Manual Review

auto_merge: false  # Always create PR for review

Best for:

  • Team taps
  • Critical packages
  • Compliance requirements

Workflow Triggers

On Release (Recommended)

on:
  release:
    types: [published]

Triggers when:

  • New release is published
  • Draft is converted to release

On Tag Push

on:
  push:
    tags:
      - 'v*'

Triggers when:

  • Any tag matching v* is pushed

Manual Dispatch

on:
  workflow_dispatch:
    inputs:
      version:
        description: 'Version to release'
        required: true

Triggers when:

  • User manually runs workflow
  • Provides version input

Multi-Formula Coordination

For releasing multiple related packages:

jobs:
  release-matrix:
    strategy:
      matrix:
        package:
          - name: core-lib
            formula: myapp-core
          - name: cli-tool
            formula: myapp-cli
          - name: plugin
            formula: myapp-plugin
      fail-fast: false
    steps:
      - name: Update ${{ matrix.package.formula }}
        uses: YOUR-ORG/homebrew-tap/.github/workflows/update-formula.yml@main
        with:
          formula_name: ${{ matrix.package.formula }}
          # ... other inputs

Troubleshooting

Issue: Workflow call permission denied

# Ensure the reusable workflow allows external calls
on:
  workflow_call:  # This enables external calling

Issue: Token doesn't have access

Check token has:

  • Correct repository scope (tap repo, not source repo)
  • Contents: Read and write
  • Pull requests: Read and write

Issue: PR conflicts

# In create-pull-request step
- uses: peter-evans/create-pull-request@v5
  with:
    # ... other options
    delete-branch: true  # Clean up after merge

Issue: Auto-merge fails

Ensure:

  • Token has PR write permissions
  • No branch protection requiring reviews
  • PR passes any required checks
# Check PR status before merge
- name: Wait for checks
  run: |
    gh pr checks $PR_NUMBER --watch

Validation Before Release

- name: Validate formula
  run: |
    # Install Homebrew (Linux)
    /bin/bash -c "$(curl -fsSL https://raw.githubusercontent.com/Homebrew/install/HEAD/install.sh)"

    # Run audit
    brew audit --strict Formula/${{ inputs.formula_name }}.rb

    # Test installation
    brew install --build-from-source Formula/${{ inputs.formula_name }}.rb
    brew test ${{ inputs.formula_name }}

Integration

Use with:

  • /craft:dist:homebrew workflow - Generate caller workflow
  • /craft:dist:homebrew setup - Full setup wizard
  • /craft:dist:homebrew audit - Pre-release validation
  • homebrew-formula-expert skill - Formula syntax and patterns

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.