Guide

Ship fixes while you sleep: Brain2Git + Claude Code auto-fix

·12 min read

You're walking, and the fix for that bug is suddenly obvious. You record it with Brain2Git. AI turns it into a proper GitHub issue. What if you didn't have to open your laptop at all — what if a pull request was just waiting for you when you did?

That's what this pipeline does: Brain2Git for capture and structuring, a self-hosted GitHub Actions runner you control, and the Claude Code CLI doing the actual engineering work — reading the issue, writing the fix, verifying it, and opening the PR. No cloud API key to manage, no GitHub Actions minutes burned, because it runs on hardware you already own.

Record voice note
Brain2Git creates issue + autofix label
Self-hosted runner triggers
Claude Code fixes it
PR opens automatically
You review & merge

The trigger is Brain2Git's AutoFix toggle on the issue preview screen — and it works two ways. Flip it on before creating the issue, and Brain2Git attaches the autofix label the moment it creates the GitHub issue. Or leave it off, create the issue, and flip it on later from the same screen — Brain2Git pushes that as a label update to the existing GitHub issue. Both paths end with the same label on the same issue; everything after that is your own infrastructure.

New: Use the GitHub Action

We've packaged this entire workflow into a reusable GitHub Action so you don't have to copy-paste 80 lines of YAML. Set up a self-hosted runner, then add 3 lines to your workflow:

.github/workflows/autofix.yml
name: Brain2Git AutoFix
on:
  issues:
    types: [opened, labeled]

jobs:
  autofix:
    if: contains(github.event.issue.labels.*.name, 'autofix')
    runs-on: [self-hosted, osx-arm64]
    timeout-minutes: 30
    permissions:
      contents: write
      pull-requests: write
      issues: write
    steps:
      - uses: akhiljimmy7/brain2git-autofix@v1

All inputs (model, base branch, install command, custom prompt) are configurable. View on GitHub →

The full manual setup is still documented below if you want to understand what the action does under the hood.

Read this before you enable it

A self-hosted runner executes workflow code on your machine. GitHub's own guidance is blunt about this: only use self-hosted runners on repositories where you trust everyone who has triage or write access — anyone who can add a label can trigger a workflow run on your hardware.

There's a second, subtler risk specific to this setup: the issue body becomes part of the prompt you hand to an agent with Bash access. A malicious or compromised collaborator could write an issue that tries to get the agent to run something unintended — classic prompt injection. The workflow below mitigates both risks:

  • A permission-check step runs first, before checkout — it verifies whoever triggered the event has write access, and exits before touching your repo or running any AI if not.
  • --allowedTools is scoped to exactly what's needed (Bash, Read, Write, Edit, Grep, Glob) — nothing broader.
  • The agent opens a pull request, never merges — a human always reviews the diff before it lands.
  • timeout-minutes: 30 caps how long a single run can occupy your machine.

If your repo has open triage access, or you don't fully trust everyone with write access, don't wire this up — or restrict it to a private repo you control alone.

Prerequisites

  • A Mac (or Linux box) that's reliably powered on — this becomes your always-on runner.
  • Admin access to the GitHub repo you want auto-fixed.
  • A Claude subscription or API access for the Claude Code CLI.
  • A repo connected in Brain2Git with the AutoFix toggle available (or trigger the label manually — the workflow doesn't care who applies it, as long as they have write access).

1. Register a self-hosted runner

Go to your repo → Settings → Actions → Runners → New self-hosted runner, pick macOS/arm64, and GitHub will show you a registration command with a repo-specific token embedded. That token is short-lived and unique to you — it can't be hardcoded into a generic script, so the setup script below stops and asks you to paste it in.

setup-runner.sh
#!/usr/bin/env bash
# setup-runner.sh — installs a GitHub Actions self-hosted runner on macOS (arm64)
# and the Claude Code CLI it will use to auto-fix issues.
#
# You still need to do two things manually (can't be scripted safely/generically):
#   1. Get YOUR repo-specific registration token from:
#      GitHub repo → Settings → Actions → Runners → New self-hosted runner
#      GitHub shows the exact ./config.sh command with a token embedded —
#      copy that command and run it where this script tells you to.
#   2. Authenticate the claude CLI interactively once (opens a browser).
#
# Usage: ./setup-runner.sh

set -euo pipefail

RUNNER_DIR="$HOME/actions-runner"
RUNNER_VERSION_HINT="Check the latest version number shown on your repo's
  Settings → Actions → Runners → New self-hosted runner page — the download
  URL and checksum are version-specific and change often, so we don't hardcode
  them here."

echo "== 1/3 — GitHub Actions runner =="
echo "$RUNNER_VERSION_HINT"
mkdir -p "$RUNNER_DIR"
cd "$RUNNER_DIR"
echo "→ Now go copy the 'Download' + 'Configure' commands from GitHub's"
echo "  New self-hosted runner page and run them in: $RUNNER_DIR"
echo "  When prompted for labels, add: self-hosted,osx-arm64"
echo ""
read -rp "Press Enter once you've run config.sh and the runner is registered... "

echo "== 2/3 — Install as a background service (always on) =="
sudo ./svc.sh install
sudo ./svc.sh start
./svc.sh status

echo "== 3/3 — Install Claude Code CLI =="
if ! command -v claude &> /dev/null; then
  npm install -g @anthropic-ai/claude-code
fi
echo "→ Run 'claude' once manually to complete browser-based authentication."
echo "  The runner service reuses this login for every job — no API key secret needed."

echo ""
echo "Done. Verify everything with:"
echo "  cd $RUNNER_DIR && ./svc.sh status"
echo "  claude --version"

Run it with chmod +x setup-runner.sh && ./setup-runner.sh. It installs the runner as a background service via svc.sh install so it survives reboots and stays “always on” without you needing a terminal open.

2. Authenticate Claude Code on the runner

The script installs the CLI via npm install -g @anthropic-ai/claude-code. Run claude once by hand to complete the browser login. The runner service reuses that session for every job — no ANTHROPIC_API_KEY secret needed, and zero GitHub Actions minutes billed, since the job runs on your own hardware.

3. Add the workflow

Drop this at .github/workflows/autofix-issue.yml in the repo you want auto-fixed. The two things this version fixes versus a naive first draft:

  • Trigger on [opened, labeled], not just [labeled] — this covers both ways the AutoFix toggle can reach GitHub. Flip it on before creating the issue, and Brain2Git attaches the label at creation time — GitHub fires opened, not labeled, for that. Flip it on later for an issue that already exists on GitHub, and Brain2Git pushes a label update instead — that's the case labeled covers. Listening to both, guarded by a contains(...) check on the label list so either event runs the same job, handles both.
  • The permission-check step described above, placed first in the job.
.github/workflows/autofix-issue.yml
# .github/workflows/autofix-issue.yml
#
# Auto-fix GitHub issues with the Claude Code CLI, running on a self-hosted
# runner you control. Triggered by the "autofix" label — including when
# Brain2Git creates the issue with the label already attached.
#
# ⚠️  SECURITY: this workflow runs arbitrary code on YOUR machine, driven by
#     GitHub issue content. Read the security section in the blog post before
#     enabling this. Do not use on repos where untrusted people have
#     triage/write access.

name: Auto-fix issue with Claude Code

on:
  issues:
    # Brain2Git (and any tool that sets labels at creation time) fires
    # "opened" — NOT "labeled" — because the label is already attached when
    # the issue is created. GitHub only fires "labeled" when a label is added
    # to an issue that already exists. Listening to both covers every case.
    types: [opened, labeled]

permissions:
  contents: write
  pull-requests: write
  issues: write

jobs:
  auto-fix:
    # Guard on the label being PRESENT, not on which event fired.
    if: contains(github.event.issue.labels.*.name, 'autofix')
    runs-on: [self-hosted, osx-arm64]
    timeout-minutes: 30

    env:
      ISSUE_NUMBER: ${{ github.event.issue.number }}
      BRANCH: fix/issue-${{ github.event.issue.number }}
      GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}

    steps:
      # ── Security gate — must run first, before checkout ─────────────
      # Only proceed if whoever triggered this event (opened the issue, or
      # added the label) has write access to the repo. Self-hosted runners
      # execute code on your machine — never skip this on a repo where
      # triage/write access is handed out loosely.
      - name: Verify sender has write access
        run: |
          SENDER="${{ github.event.sender.login }}"
          PERMISSION=$(gh api "repos/${{ github.repository }}/collaborators/${SENDER}/permission" --jq '.permission')
          echo "Sender: $SENDER — permission: $PERMISSION"
          if [[ "$PERMISSION" != "admin" && "$PERMISSION" != "write" ]]; then
            echo "::error::$SENDER does not have write access. Refusing to run auto-fix."
            exit 1
          fi

      - name: Checkout
        uses: actions/checkout@v4
        with:
          fetch-depth: 0

      - name: Create fix branch
        run: git checkout -b "$BRANCH"

      - name: Install dependencies
        run: npm ci

      - name: Run Claude Code agent
        run: |
          claude --allowedTools "Bash,Read,Write,Edit,Grep,Glob" \
                 --model claude-sonnet-4-6 \
                 -p "
          Fix GitHub issue #${ISSUE_NUMBER} in this repository.

          ## Step 1 — Read the issue
          Run: gh issue view ${ISSUE_NUMBER} --json title,body,labels,comments

          ## Step 2 — Load context
          1. Read CLAUDE.md at the repo root — your operating manual.
          2. Understand the issue type: copy fix, bug, UX change, or structural change.

          ## Step 3 — Implement
          Make the minimum change that resolves the issue. No scope creep.

          ## Step 4 — Verify
          Run this project's type-check, test, and lint commands (see CLAUDE.md
          or package.json scripts). Fix any failures before finishing.

          ## Non-negotiables
          - Fix ONLY what the issue describes.
          - No hardcoded secrets.
          - Follow the existing code style — don't introduce new patterns.

          If the issue needs something you can't verify (e.g. a schema migration
          you can't test), or is too ambiguous, comment on the issue explaining
          why and stop. Do not guess and ship broken code.
          "

      - name: Check for changes
        id: changes
        run: |
          git add -A
          if git diff --cached --quiet && [ "$(git rev-list --count origin/main..HEAD)" -eq 0 ]; then
            echo "has_changes=false" >> "$GITHUB_OUTPUT"
          else
            echo "has_changes=true" >> "$GITHUB_OUTPUT"
          fi

      - name: Commit and push
        if: steps.changes.outputs.has_changes == 'true'
        run: |
          if ! git diff --cached --quiet; then
            git commit -m "fix: resolve issue #${ISSUE_NUMBER}

          Co-Authored-By: Claude <noreply@anthropic.com>"
          fi
          git push --force origin "$BRANCH"

      - name: Open PR
        if: steps.changes.outputs.has_changes == 'true'
        run: |
          # Unset GH_TOKEN so 'gh' falls back to the runner's own logged-in
          # account instead of the ephemeral Actions token. PRs opened with
          # GITHUB_TOKEN don't trigger other workflows (a GitHub restriction) —
          # this keeps your normal CI running on the bot's PR.
          unset GH_TOKEN
          ISSUE_TITLE=$(gh issue view "$ISSUE_NUMBER" --json title --jq '.title')
          gh pr create \
            --title "fix: ${ISSUE_TITLE} (closes #${ISSUE_NUMBER})" \
            --body "Automated fix for #${ISSUE_NUMBER}, generated by Claude Code.

          ## Test plan
          - [ ] Type-check passes
          - [ ] Tests pass
          - [ ] Manual verification

          🤖 Generated via the Brain2Git auto-fix workflow" \
            --head "$BRANCH" \
            --base main

      - name: Comment if no changes were made
        if: steps.changes.outputs.has_changes == 'false'
        run: |
          gh issue comment "$ISSUE_NUMBER" \
            --body "🤖 Claude Code looked at this issue but didn't make changes — it may need manual clarification. Check the workflow run for details."

      - name: Clean up runner workspace
        if: always()
        run: |
          rm -rf node_modules .next
          git push origin --delete "$BRANCH" 2>/dev/null || true
          git checkout main 2>/dev/null || true
          git branch -D "$BRANCH" 2>/dev/null || true
          git clean -fd --exclude=node_modules 2>/dev/null || true
          git remote prune origin 2>/dev/null || true

4. Connect the repo in Brain2Git and test it

  1. In Brain2Git, connect the repo you just wired up (Dashboard → Connect Repository).
  2. Record a voice note describing a small, well-scoped bug or change.
  3. On the issue preview screen, toggle AutoFix on before creating the issue — or create it first and toggle AutoFix on afterward, then hit Save. Either way, Brain2Git gets the autofix label onto the GitHub issue.
  4. Watch your runner — cd ~/actions-runner && ./svc.sh status — or the Actions tab in GitHub.
  5. A branch, a commit, and a pull request should appear within a couple of minutes.

Customizing it for your project

The prompt inside the workflow references a CLAUDE.md at your repo root — that's your chance to give the agent house rules: how to run tests, what patterns to follow, what's off-limits. The more specific your CLAUDE.md, the tighter the fixes.

Model IDs move fast — claude-sonnet-4-6 is a solid default for scoped issue fixes; swap in a larger model for gnarlier structural changes. Check claude --help for whatever's current when you set this up.

Haven't tried the recording side yet?

Start with Brain2Git