Bug Triage Swarm
MIT↓ 0 downloadsAn Opus planner triages your open `bug`-labelled GitHub issues into independent fixes, then a swarm of Sonnet fixers fans out — one per issue, each on its own branch — to reproduce, root-cause, patch, and test the bug in parallel.
Topology
Disclosures
Everything below runs on your machine or inside the sandbox when you use this workflow. Mismatches between these declarations and the actual code block publishing.
Host hooks
Commands executed on YOUR host machine by Sandcastle lifecycle hooks.
None declared.
Sandbox hooks
Commands executed inside the sandbox container.
npm install
Network access
Runs `gh` inside the sandbox to list issues labelled `bug` and to view the body of each issue being fixed on the current repository. No other hosts are contacted.
Shell expansion
Prompt files contain !`command` blocks — the agent CLI executes these commands at prompt-load time. They are highlighted amber in the prompt files below.
Files
Diff vs the stock Sandcastle 0.12.0 template Dockerfile — green lines were added by the author, red lines were removed from stock.
+# Sandbox image for the Bug Triage Swarm workflow.+# Node 22 + git + the GitHub CLI (the planner and fixers read issues via `gh`)+# + the Claude Code CLI, running as a non-root `agent` user.FROM node:22-bookworm-# System dependencies.+# System dependencies used by the agents and the gh issue blocks.RUN apt-get update && apt-get install -y --no-install-recommends \git \curl \jq \ca-certificates \&& rm -rf /var/lib/apt/lists/*-# Claude Code CLI (the agent runtime).+# GitHub CLI — used by the !`gh ...` shell-expansion blocks in the prompts.+RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \+ | dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \+ && echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \+ > /etc/apt/sources.list.d/github-cli.list \+ && apt-get update && apt-get install -y --no-install-recommends gh \+ && rm -rf /var/lib/apt/lists/*++# Claude Code CLI (the planner and fixer agent runtime).RUN npm install -g @anthropic-ai/claude-code# Non-root agent user. `sandcastle docker build-image` aligns AGENT_UID/GID to# the host user via --build-arg to avoid permission errors on bind mounts.+# node:22-bookworm already ships a "node" user at UID/GID 1000, so we RENAME it+# (the stock Sandcastle template pattern) — groupadd/useradd would collide with+# the existing IDs on a default build.ARG AGENT_UID=1000ARG AGENT_GID=1000-RUN groupadd --gid ${AGENT_GID} agent \- && useradd --uid ${AGENT_UID} --gid ${AGENT_GID} --create-home --shell /bin/bash agent+RUN groupmod -o -g ${AGENT_GID} node \+ && usermod -o -u ${AGENT_UID} -g ${AGENT_GID} -d /home/agent -m -l agent node-USER agent-WORKDIR /workspace+USER ${AGENT_UID}:${AGENT_GID}+WORKDIR /home/agent++# Sandcastle bind-mounts the worktree and sets the working directory at+# container start; the container just needs to stay alive until then.+ENTRYPOINT ["sleep", "infinity"]
Show full Dockerfile (highlighted)
# Sandbox image for the Bug Triage Swarm workflow.
# Node 22 + git + the GitHub CLI (the planner and fixers read issues via `gh`)
# + the Claude Code CLI, running as a non-root `agent` user.
FROM node:22-bookworm
# System dependencies used by the agents and the gh issue blocks.
RUN apt-get update && apt-get install -y --no-install-recommends \
git \
curl \
jq \
ca-certificates \
&& rm -rf /var/lib/apt/lists/*
# GitHub CLI — used by the !`gh ...` shell-expansion blocks in the prompts.
RUN curl -fsSL https://cli.github.com/packages/githubcli-archive-keyring.gpg \
| dd of=/usr/share/keyrings/githubcli-archive-keyring.gpg \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/usr/share/keyrings/githubcli-archive-keyring.gpg] https://cli.github.com/packages stable main" \
> /etc/apt/sources.list.d/github-cli.list \
&& apt-get update && apt-get install -y --no-install-recommends gh \
&& rm -rf /var/lib/apt/lists/*
# Claude Code CLI (the planner and fixer agent runtime).
RUN npm install -g @anthropic-ai/claude-code
# Non-root agent user. `sandcastle docker build-image` aligns AGENT_UID/GID to
# the host user via --build-arg to avoid permission errors on bind mounts.
# node:22-bookworm already ships a "node" user at UID/GID 1000, so we RENAME it
# (the stock Sandcastle template pattern) — groupadd/useradd would collide with
# the existing IDs on a default build.
ARG AGENT_UID=1000
ARG AGENT_GID=1000
RUN groupmod -o -g ${AGENT_GID} node \
&& usermod -o -u ${AGENT_UID} -g ${AGENT_GID} -d /home/agent -m -l agent node
USER ${AGENT_UID}:${AGENT_GID}
WORKDIR /home/agent
# Sandcastle bind-mounts the worktree and sets the working directory at
# container start; the container just needs to stay alive until then.
ENTRYPOINT ["sleep", "infinity"]
# Auth for the Claude Code planner and fixer agents.
# Run `claude setup-token` on your host to generate a token, then paste it here
# in your local .sandcastle/.env (never commit the real .env).
CLAUDE_CODE_OAUTH_TOKEN=
# Optional: used by `gh` inside the sandbox to read issues.
# Only needed when your repository is private.
GITHUB_TOKEN=
Fix bug #{{ISSUE_NUMBER}}
You are a focused bug-fixer working on branch agent/bug-{{ISSUE_NUMBER}}. Your
job is to resolve a single reported bug end to end.
Here is the issue you must fix:
!gh issue view {{ISSUE_NUMBER}} --json title,body -q "\(.title)\n\n\(.body)"
Your task
- Reproduce. Read the issue carefully and find the code path it describes. Write a failing test that reproduces the bug first, if the repository has a test suite.
- Fix the root cause. Make the smallest change that correctly fixes the underlying problem — not just the symptom. Do not paper over it.
- Cover it. Make sure your reproduction test now passes, and that the existing test suite still passes. Add edge-case coverage where it matters.
- Explain. In your commit message, reference the issue (
fix: #{{ISSUE_NUMBER}} <summary>) and briefly state the root cause and the fix.
Keep commits small and focused. If the issue is unclear or you cannot reproduce it, document what you tried and what you would need, rather than guessing.
When the bug is fixed and the suite is green, output the exact line
<promise>COMPLETE</promise> and stop.
import { run, claudeCode } from "@ai-hero/sandcastle";
import { docker } from "@ai-hero/sandcastle/sandboxes/docker";
// 1. Triage — an Opus planner reads the open issues labelled `bug` (via a
// `gh issue list` shell-expansion block in plan-prompt.md), groups them into
// independent fixes, and notes which issue each fix belongs to. The sandbox
// hook installs dependencies so the planner can reason about the codebase.
const triage = await run({
name: "triage",
agent: claudeCode("claude-opus-4-8", { effort: "high" }),
sandbox: docker(),
promptFile: ".sandcastle/plan-prompt.md",
maxIterations: 1,
hooks: {
sandbox: {
onSandboxReady: [{ command: "npm install", timeoutMs: 300_000 }],
},
},
});
console.log(`Triage finished in ${triage.iterations.length} iteration(s).`);
// In a real run you would parse the triage output to discover the issues to fix.
// For a deterministic package we fan out over a fixed demo list of issue numbers.
// Replace these with the `bug`-labelled issues you actually want fixed.
const issueNumbers = ["201", "202", "203"];
// 2. Fix — fan out one Sonnet fixer per issue, each on its own branch. Distinct
// branches make the parallel fan-out safe (concurrent runs never share HEAD).
// Each fixer pulls its issue body with `gh issue view` (see fix-prompt.md).
await Promise.all(
issueNumbers.map((issue) =>
run({
name: `fix-${issue}`,
agent: claudeCode("claude-sonnet-4-6"),
sandbox: docker(),
promptFile: ".sandcastle/fix-prompt.md",
promptArgs: { ISSUE_NUMBER: issue },
branchStrategy: { type: "branch", branch: `agent/bug-${issue}` },
maxIterations: 4,
hooks: {
sandbox: {
onSandboxReady: [{ command: "npm install", timeoutMs: 300_000 }],
},
},
}),
),
);
// 3. Done — each fix now lives on its own `agent/bug-<n>` branch, ready to review
// and merge. (In a real pipeline you would open a PR per branch here.)
console.log(`Fixed ${issueNumbers.length} issue(s) on distinct branches.`);
Triage the open bug reports
You are the triage lead. Below are the currently open issues labelled bug on
this repository:
!gh issue list --state open --label bug --json number,title,body --limit 100
Here is recent history for context:
!git log --oneline -10
Review each bug and group the issues into independent fixes that can be implemented on separate branches without conflicting with one another. For each fix, note:
- The issue number it corresponds to.
- A one-line description of the suspected root cause.
- Which files or modules the fix most likely touches (so overlapping fixes are grouped or flagged).
Rank the fixes by severity and blast radius, most urgent first. If two issues are actually the same underlying bug, say so and merge them.
Output a numbered triage plan, one fix per line. When the plan is complete,
output the exact line <promise>COMPLETE</promise> and stop.
README
Bug Triage Swarm
Turn a backlog of open bug reports into a stack of ready-to-review fixes — in
parallel. This workflow triages your bug-labelled GitHub issues, then dispatches
a swarm of agents that each own one bug end to end: reproduce it, find the root
cause, patch it, and prove the fix with tests.
What it does
First, a Claude Code planner (Opus) reads every open issue labelled bug and
groups them into independent fixes. It notes the suspected root cause, the files
each fix likely touches, and flags duplicates or overlaps so two agents never
fight over the same code.
Then a swarm of fixers (Sonnet) fans out — one agent per issue, each on its
own agent/bug-<n> branch. Every fixer pulls its own issue body, writes a failing
reproduction test, fixes the root cause (not just the symptom), and makes sure the
suite goes green before committing with a fix: #<n> message.
How it works
main.ts runs the planner once, then uses Promise.all to fan out the fixers,
each pinned to a distinct branch so the parallel runs never collide on HEAD. Both
the planner and the fixers read issues through !`gh issue list` and
!`gh issue view` shell-expansion blocks inside their prompts — this is why
the manifest discloses network access (via gh) and shell expansion. The topology
is triage → fix ×N (fan-out) → fixes on branches. A npm install sandbox hook
warms dependencies so every agent can run the test suite.
Requirements
Set CLAUDE_CODE_OAUTH_TOKEN in .sandcastle/.env (run claude setup-token). For
private repositories, also set GITHUB_TOKEN so gh can authenticate. Label the
issues you want handled with bug, and edit the issueNumbers demo list at the
top of .sandcastle/main.ts to the issues you actually want fixed. Build the image
once with npx @ai-hero/sandcastle docker build-image, then run
npx tsx .sandcastle/main.ts. Each fix lands on its own branch, ready to open as a
PR and review.