The agent doesn't pick what to work on. The board does.
Sixteen repos, one queue, three slash commands, six review agents that wake by path. This page is the whole setup — the discipline, the plumbing, every option I considered, and the four things that blew up while installing it.
Screenshots are reconstructions with decoy repo names — the real boards carry client work. The numbers are real.
Jump to section tap to open
The 30-second answer
Work flows one way: an idea becomes a GitHub issue written for an agent with no memory of the conversation; the issue sits on a GitHub Projects board whose queue answers "what can I start"; a session picks one up, branches, implements inside the issue's stated boundary, runs the checks, and gets reviewed by six read-only agents routed by file path; every finding is fixed or dismissed in writing inside the PR; CI is the only gate. All of it is a Claude Code plugin — three slash commands, six subagents, and one committed JSON config per repo. Setup for a new repo is about ten minutes.
The problem: a fleet with no queue
Run one Claude Code session and you are the queue — you know what it's doing because you asked for it. Run five sessions across a dozen repos and three failure modes arrive on schedule:
- Invented work. An agent finishes early and "improves" adjacent code nobody asked about. The diff grows 4× past the ask, and review time grows with it.
- Colliding branches. Two sessions touch the same file from different intentions. You find out at merge time, which is the most expensive time.
- Unreviewable output. Plausible 800-line PRs with no statement of what was verified. Most AI code looks right on its face — that's the problem, not the reassurance.
The fix isn't a smarter agent. It's the same thing that fixes human teams: a queue with a definition of ready, a boundary per task, and a review step that can't be skipped silently. Agents follow process better than people do — they just need the process to be structure, not vibes.
The five rules everything hangs off
- One issue = one PR = one concern. Bigger work is a parent issue with sub-issues. The size test: a diff a reviewer can hold in their head (~400 lines).
- An issue is written for an agent with no memory of the conversation that produced it. Concrete file paths, an explicit "must not touch" list, acceptance criteria a third party can check without reading the diff.
- The board answers "what can I start" — not the issue tracker. Ready means the spec passed the bar and nothing open blocks it.
- Review happens before a human sees the branch, and the result is written into the PR: what ran, what it found, what was fixed, what was dismissed and why.
- Nothing an agent says is a gate. Lint, types, tests — deterministic checks gate. Agents inform. A finding binds in one way only: it must be answered, in writing.
The issue is the spec
The issue form is where the leverage is. Mine has eight required fields; three do most of the work:
- Entry points. The paths this task lives in — and the paths it must not touch. This is what stops an implementer guessing where code goes, and what keeps two concurrent branches off each other's files. If you can't name the paths, you haven't finished thinking.
- Acceptance criteria. Checkboxes someone else can verify without reading the diff. "Filtering works" is not one. "An unknown owner id returns 422 and the list is unchanged" is.
- Open questions. Anything guessed or undecided. Non-empty means the issue is not ready — it goes to Refining, not the queue. Writing
noneis a claim you get held to.
The pickup command refuses an issue that fails this bar. That refusal felt like friction for a day. It's the feature: a vague issue produces a plausible PR that solves the wrong problem, and that costs more review than the refusal ever will.
The board: one queue over many repos
GitHub Projects v2, one board per product ecosystem. Seven statuses — Inbox, Refining, Ready, In progress, In review, Blocked, Done — plus a Priority single-select. Issues from any number of repos sit on the same board; each repo's config names the same project number.
The part GitHub doesn't give you out of the box: gh issue view shows the body and labels and nothing else — status, priority, and the blocking graph live on the board and in the dependencies API, behind GraphQL. So the board gets a vendored script, board.sh: add, show, set, block --by, ready, queue. The queue is the important one — it is not "status equals Ready", it is Ready and every blocking issue is closed. Planning ahead only works if the queue hides work that can't start yet.
The three commands
The lifecycle is three slash commands in a Claude Code plugin. Each one reads the repo's committed config and refuses to run without it.
/task <idea> — turns a sentence into a filed, board-tracked issue. It reads the codebase first (real paths, not imagined ones), checks for duplicates, writes the eight-field body, files it with area/risk labels, sets board status — Ready only if every readiness check passed, Refining if anything was guessed. It asks at most three questions, and only where two readings produce materially different work.
/pick <n> — takes issue #n to an open draft PR. Verifies readiness (and refuses), branches feat/41-slug — the number is load-bearing, it's how every later tool finds the issue — implements inside Entry points, runs the issue's verification commands, runs the review fleet, opens the draft with the fleet's results in the body. Called with no number, it prints the ready queue and asks which.
/review — runs the fleet on the current branch. Asks review.sh plan which agents this branch wakes, spawns exactly those in parallel, merges and dedupes findings, then fixes or dismisses every one with the reason written down. It never adds an agent the routing didn't name — that's what makes the routing worth having.
The review fleet: six agents, path-routed
Six read-only subagents, each owning one question. Which of them wake on a branch is decided by glob matching over the changed paths — a shell script, not a model's mood:
- scope — compares changed files against the issue's Entry points and must-not-touch list. Reads paths, never code. Cheapest agent, wakes on every branch.
- simplicity — abstraction with one caller, local reimplementations of things the repo provides, defensive code for states the types exclude.
- test-quality — the only question that matters: if this behaviour broke tomorrow, would anything here go red? A test that passes against a broken implementation is worse than no test.
- security — auth, tenant isolation, injection, secrets, what a log line exposes. Its bar: no finding without a concrete failure scenario — a request, a role, and a state that together produce the wrong outcome. Also wakes on any issue labeled
risk/high, whatever the diff touches. - db — migrations, expand/contract ordering, whether an index matches the query the code actually issues, N+1s, transaction boundaries.
- contract — additive vs breaking on your public API surface. Off in most repos; on where an SDK or versioned API exists.
And the output side — the PR carries the whole conversation:
Technical setup, start to finish
The whole thing is a plugin plus per-repo files. Nothing repo-specific lives in the plugin; nothing generic lives in the repo.
1. The plugin (installed once, user scope):
my-workflow-plugin/
├── .claude-plugin/plugin.json # name, version
├── commands/ # task.md, pick.md, review.md — the slash commands
├── agents/ # six reviewer .md files (tools: Read/Grep/Glob/Bash,
│ # permissionMode: plan — read-only by construction)
├── scripts/ # board.sh, review.sh, sync-labels.sh, board-bootstrap.sh
└── templates/ # issue forms, PR template, workflow docs, config skeleton 2. Per repo — a setup command vendors these in and commits them:
.claude/workflow-kit.json # THE config: repo, board number, review routing,
# deterministic checks, labels, protected paths
.github/scripts/ # the four scripts, copied — a human and CI can run them
.github/ISSUE_TEMPLATE/ # task / bug / docs forms (projects: line auto-boards them)
.github/pull_request_template.md # with the Reviewer notes section the fleet fills
docs/workflow/ # board rules, branching, review rules
CLAUDE.md # the working agreement, appended 3. The config is the whole game. Two keys decide whether the kit is useful in a repo:
"review": {
"deterministicChecks": [ // what agents must NOT report on
{ "command": "pnpm lint", "decides": "lint rules" },
{ "command": "pnpm test", "decides": "whether the unit tests pass" }
],
"agents": { // the routing table
"scope-reviewer": { "enabled": true, "always": true },
"security-reviewer": { "enabled": true, "wakesOnRiskHigh": true,
"paths": ["src/server/*", ".github/workflows/*"] },
"db-reviewer": { "enabled": true, "paths": ["prisma/*"] }
}
} Write the globs against paths the repo actually has — an agent routed to a directory that doesn't exist never wakes and never says why. I validate every glob against git ls-files at install time; it caught three dead routes across sixteen repos.
4. The board, one-time: gh auth refresh -s project (the default token can't touch Projects), then a bootstrap script that creates the project, replaces the stock three Status options with the seven via one GraphQL mutation (gh project field-create can't edit the built-in Status field), creates Priority, and — the part most setups skip — reads the field list back to prove the write took before writing the project number into any config.
The options, judged
Every fork I hit, and what I'd pick again:
- One board per repo vs shared. Per-repo works unmodified and gives you no portfolio view — you end up with fifteen tabs. Shared board per ecosystem is the keeper: repos that ship together share a queue; the queue command filters to the current repo by default and shows everything with
--all. One global board across unrelated product families is noise wearing a uniform. - Agents block vs inform. Covered above — inform, always. The written-answer contract is stronger than a gate and can't be gamed by rewording the prompt.
- Scripts in the plugin vs vendored into the repo. Vendor them. A person can run
board.sh readyfrom a terminal, CI can run them, an agent that isn't Claude Code can read them. The plugin is the distribution channel, not the runtime. - Model per reviewer. Five of six run on the session's model — "is this abstraction earning its keep" is not pattern matching. The scope reviewer is set arithmetic over path lists and runs on the cheapest model available. Cost discipline goes in the routing, not the model tier.
- Where the config lives. Committed, in the repo, one file. Context should travel with the artifact it describes — a session opening the repo finds the whole workflow self-described, no side channel.
- Solo vs team. This entire setup is tuned for one operator running many agents. A human team gets most of the value from just rules 1–2 (issue discipline) and the PR template; add the fleet when review becomes the bottleneck.
The nightly pulse that keeps it honest
Boards rot when nobody looks. The last piece is a nightly job — plain bash on a scheduler, no LLM, zero tokens — that walks every repo and writes one note into my vault:
Boards per group, CI on main for all sixteen repos (newest run per workflow, so a skipped no-op can't mask the real one), open PRs, and — the part with teeth — a two-strike rule: a check red on two consecutive nights emits the exact /task command that files the fix as a board issue. Signal becomes queued work or it evaporates; there is no third state where it lingers in someone's head.
Every section ends in ok or UNREADABLE — a source that fails prints its failure instead of disappearing. The first run caught two CI workflows that had been silently red on main; nothing else was aggregating them.
What blew up during install
Sixteen repos in one pass. Fourteen went clean. The receipts from the rest:
- Stray git hooks had been blocking every commit in one repo for a week — a husky install from some npx run had landed in the wrong git root, and every hook exited 1. Nothing surfaces this; commits just fail. Now the install script lists non-sample hooks before touching any repo.
- A local pre-push gate failed in a clean worktree because it shells into a toolchain the worktree doesn't have. Environmental failure, not a verdict on the change — that repo gets a branch + PR and CI decides, which is what the rules said all along.
- An LLM-judge CI check went red on a config-only PR. Diff couldn't reach the judged surface; the same check had passed on main twice that day. Policy now: prove the diff can't touch it, one rerun, then a human. Rerunning a judge until it's green is laundering a signal.
- A green local validation shipped a red CI —
node --testwith a glob pattern finds zero tests on Node 20 (glob support landed in 21) and my machine runs 26. Validate with CI's runtime version, or you're validating a different program.
Do this Monday
- Pick one repo. Write the issue form with Entry points, must-not-touch, checkable acceptance criteria, and a required Open-questions field where
noneis a claim. - Create the board:
gh auth refresh -s project, seven statuses, Priority. Wire the form'sprojects:line so filed issues land on it automatically. - Write
.claude/workflow-kit.json— or your equivalent — with the deterministic checks and two reviewer routes (scope always; security on your server paths). Commit it. - File one real issue with
/task. Let/picktake it to a draft PR. Read the Reviewer notes it produces. - Only then decide how much more you want. The issue discipline alone is half the value; the fleet is the other half; the nightly pulse is what keeps both alive in month three.
FAQ
Can Claude Code work with GitHub Projects? Yes, via gh — Projects v2 is GraphQL-only, so wrap the queries in a small committed script and have the agent call that. One command for "what can I start" beats re-deriving GraphQL per session, and humans and CI get the same interface.
How is this different from just writing a good CLAUDE.md? CLAUDE.md is preference; this is structure. The commands refuse to run without the config, the issue form refuses vagueness, the routing table decides who reviews. An agent can talk itself past prose. It can't talk itself past a script that won't produce a queue entry.
Do the review agents write code? No — read-only by tool allowlist (Read/Grep/Glob/Bash, plan mode). They return findings; the implementing session applies fixes. One writer per branch keeps the diff attributable.
What does this cost per PR? The fleet is the marginal cost: typically three to five agents on the session model plus one on the cheapest tier, a few minutes wall-clock since they run in parallel. The refusal steps cost nothing and save the most — the expensive PR is the plausible one that solved the wrong problem.
Does this work without Claude Code? The board scripts, issue forms, PR template, and CI checks are plain GitHub — any agent (or human) can follow them. The slash commands and subagent fleet are the Claude Code layer; the discipline underneath is portable.