Skip to Content

/drain

Drain the ready queue autonomously — the overnight workhorse that loops /work-ticket/review-pr → agent-merge gate → post-merge validation for each safely-classified bead while you sleep.

When to use it

Use /drain when the ready queue is groomed, every candidate bead carries a safety:* label, and you want the framework to advance multiple beads through the full implementation cycle without attending each step. The drain pre-flights production health, snapshots the queue via bd ready --json, filters by safety class, and works through the list one bead at a time. Each bead gets its own PR, CI run, reviewer verdict, merge gate, and production validation before the next bead starts.

/drain is specifically the merge loop — it moves code from ready to closed. For promoting a safety:flagged feature from “dark on production” to “visible to users,” reach for /goal.

V1 trade-off: the operator’s machine must stay on and the Claude Code session must stay open. Remote unattended operation is a future ticket. You make the flag-flip release decision in the morning; drain only ships code, never lifts a feature flag.

Safety classes

The drain filters the ready snapshot by safety class before touching anything:

ClassIncluded by defaultFlag to include
safety:internalYes(always)
safety:flaggedYes(always)
safety:reversibleNo--include-reversible
safety:hotNeverdrain always refuses

For the full taxonomy and decision tree, see docs/safety-classes.md.

How it fits in the lifecycle

bd ready is the queue, not a column. The drain reads its snapshot at run start and does not pull new arrivals mid-run.

What it does

Pre-flight

Before setting the loop condition the drain verifies:

  • gh CLI is authenticated as va-worker
  • .beads/ exists; bd ready --json is valid JSON; bd dep cycles --json returns [] (cyclic graph = STOP)
  • agent-merge.yml and drain-merge-bridge.yml exist on main
  • safety:* label vocabulary is seeded in both the repo (PR labels) and the beads tracker
  • Every configured external signal (deploy-status service, error-rate baseline) points at this repo — not a different product’s service
  • Production baseline is healthy (node scripts/sentry-baseline.mjs must be ≤2× the 24-hour median; fails open with an abort notice)

Pre-flight fails closed: any failed check stops the drain and reports to the operator before a single bead is touched.

Snapshot, sort, filter

After pre-flight clears, the drain takes an immutable snapshot:

bd ready --json | jq '[.[] | select(.issue_type == "task") | select(((.labels // []) | map(select(startswith("safety:"))) | length) == 1)]'

Beads are ordered by priority, then bead id. bd ready is already blocker-aware — blocked beads never appear in the output. The hand-rolled topo-sort of the GitHub-Projects era is retired; the tracker owns the dependency graph.

A drain-run bead is created as the audit anchor:

bd create "Drain run $(date -u +%Y-%m-%dT%H:%MZ)" --type=chore -l drain-run

Every per-bead audit comment and the end-of-run close land on this bead.

Per-iteration cycle (one bead per turn)

For each bead in the snapshot:

  1. Claim atomicallybd update <id> --claim. If the claim fails another worker has it; skip to the next. The claim is the in-progress signal — there is no board to move anything on.

  2. Invoke /work-ticket <bead-id> with DRAIN_CONTEXT=true. The worker handles branch, code, CI, and the chained /review-pr; it applies in-review + pr:<N> labels and copies the bead’s safety:* label onto the PR (the bead is the label source of truth).

  3. If CI is red after 3 fix attempts — apply drain-blocked label, post a bd comment explaining what failed, increment the consecutive-failure counter, end the turn.

  4. If the review verdict is NO-GO (verdict:no-go label) — same as above.

  5. If CI is green and verdict is GO (verdict:go):

    • Wait for the full check rollup to reach a terminal conclusion (nothing PENDING / IN_PROGRESS / QUEUED). Time budget: 15 minutes.
    • Dispatch the drain-merge bridge via repository_dispatch. The bridge (.github/workflows/drain-merge-bridge.yml) calls agent-merge.yml via workflow_call — the only path through which the gate permits merges per its safety contract.
  6. If the gate denies the mergedrain-blocked label + bd comment naming the denial reason.

  7. Wait for the deploy — poll the deploy plane until the merge’s deploy is live (10-minute budget). Skip entirely when no deploy plane is configured.

  8. Run post-merge validation — smoke tests or a content-marker curl probe. For safety:internal beads the lightweight curl probe is acceptable.

  9. Verify before close — only after gh pr view <N> --json state,mergedAt confirms the merge:

    bd close <id> --reason "PR #N merged to main via agent-merge gate, deploy validated"

    Workers never bd close. Drain does — but only here, only after verified merge.

Audit trail

After each turn the drain posts a bd comment on the worked bead naming: the drain-run bead id, the outcome (Done / Blocked / Blocked-Rolled-Back / Merged-NotDeployed / Skipped), the PR number, the merge commit, and the Sentry baseline before vs after. A one-line entry also lands on the drain-run bead so the full sequence is readable in one place.

Wake-up summary

When the loop condition is met (or the drain aborts), the per-bead outcomes are piped through scripts/emit-drain-summary.mjs and printed to the operator. The summary covers shipped, blocked, rolled-back, merged-not-deployed, and skipped beads, plus the production status and recommended morning actions. The drain-run bead is closed with a summary reason at the end.

Agent-merge gate

The agent-merge gate (agent-merge.yml) is the autonomous-merge path in Gemba Flow. The drain dispatches it via drain-merge-bridge.yml (repository_dispatchworkflow_call); it re-verifies 7 conditions before every merge:

  1. The PR body carries a Bead: <id> citation
  2. The PR carries a safety:* label
  3. Every check in the rollup is conclusive and green
  4. The PR is open against main
  5. No do-not-merge label is present
  6. (Configurable) A second-identity reviewer approval is present
  7. The dispatch came through the bridge, not a direct gh pr merge

The gate is opt-in and configurable: REVIEWER_APPROVAL_LOGIN keeps the second-identity requirement (upstream default); forks that deliberately empty this config get a loud notice. Operator-merge remains the shipped default posture — the gate is the autonomous path for drain runs that have earned the trust.

The drain never calls gh pr merge directly. The gate is the only path.

State persistence and resumption

The drain writes state to disk at 9 trigger points (/tmp/drain-state-<drainId>.json) so a /drain --resume can re-enter after an API interruption without re-doing already-shipped work. All writes go through scripts/write-drain-state.mjs (schema-validated, atomic write). See docs/drain-resumption.md for the mechanism and edge cases.

Invocation shapes

/drain # safety:flagged + safety:internal; no end time /drain --until 06:00 # stop at 6 am if the queue is not empty by then /drain --include-reversible # opt in to safety:reversible beads (rate-limited) /drain --max-tickets 5 # cap the number of beads processed /drain --dry-run # emit the plan summary; do not execute /drain --resume <drainId> # resume an interrupted drain run /drain --resume # auto-detect the most recent interrupted run

--dry-run runs everything through snapshot + sort + filter + plan emit, then stops without setting the loop condition. It still creates and closes a drain-run bead so the plan has an audit anchor.

Honest limits (v1)

  • Operator-active. The machine must stay on; the session must stay open. True unattended overnight runs are a future API-bridge ticket (see ADR-006 in docs/TECHNICAL-ARCHITECTURE.md).
  • No human write UI. Board projections (.gembaflow-boards/{kanban,techtree}.html) are read-only; board state is mutated via bd in the operator terminal.
  • safety:hot is always refused. Hot beads wait for the operator; drain posts an audit note on each refused bead and continues.
  • safety:flagged ships dark. The drain merges code behind feature flags; it never lifts a flag. The morning release decision belongs to the operator.
  • /groom-backlog — makes beads ready before invoking /drain
  • /work-ticket — the per-bead workhorse drain calls internally
  • /review-pr — auto-chained from /work-ticket on green CI
  • /goal — picks up from where /drain leaves off; promotes a dark-shipped safety:flagged feature to GA

Canonical spec: .claude/commands/drain.md

Last updated on

Book a Demo