feat(work-implement): group a worktree drain's PRs behind one queue branch - #217
Conversation
…ranch Add the opt-in `work.queueBranch` gate. Under `branch: worktree` every issue PR targets `ai/queue-<hash>` instead of `pr.base`, and the drain opens a single `ai/queue-<hash>` -> `pr.base` PR to close the run, reusing one already open rather than cutting a second beside it. The drain never merges or fast-forwards that PR: the target repo's own workflow lands it, so no bypass-capable credential sits on the machine running the loop. A fast-forward carries every commit across unchanged, which keeps one CHANGELOG entry per issue and leaves `allowed_merge_methods` on the base branch untouched. Off by default, because nothing in a drain can verify that landing workflow exists and its absence fails silently, stranding every issue PR on a branch nothing merges. The CI saving likewise depends on the repo scoping its workflow triggers to the integration branches, so it is documented rather than promised. Refs #175
|
AI review — changes requested (round 1 of 3, head The block is genuinely lifted — checked first, and it holdsThis issue was
So the implementation rests on a recorded decision, not on an assumption, and shipping ahead of the workflow is sound because the gate defaults off. The Requirements coverage: all six "What to build" boxes are addressed — retargeted base, reuse-before-cut, drain opens the queue PR, drain never lands it and reports it, opt-in config gate that stops loudly, and the CI saving documented as conditional rather than promised. No scope creep: every hunk traces to one of the six. No secrets in the diff. Gate: Two things to fix before this is accepted. 1. The REFERENCE table points at the step the SKILL rules out
The mechanic is not at step 1. It is at step 5 (Drain), and
Step 1 in that skill is Load config & lock. An agent that follows the table rather than the body cuts the queue branch and opens a queue PR before the queue is built — so an empty queue leaves a stray Fix: make the row read step 5 for the cut/reuse-and-open and step 6 for the report, matching the 2. Nothing states the one condition under which the fast-forward stops being possible
A fast-forward is only available while the queue head is still a descendant of That is the exact failure this mode's opt-in gate is justified by — "green, reviewed, and stranded on a branch nothing merges" — reached by a second route the prose does not mention. The reuse-before-cutting rule widens the window rather than narrowing it: a queue PR reused across several drains is long-lived by design, so it has more time to fall behind. I am not asking the drain to grow the power to land or rebase it — the split of duties is right, and it is arguably Checked, no change neededUnder this mode issue PRs target Round 1 of 3, so this returns to the implement loop rather than escalating. |
…-forward The Queue branch table sent the drain's cut/open to step 1 (load config & lock), which the queue SKILL argues against in the same breath: cutting before the queue is built strands an ai/queue-<hash> PR whenever the queue turns out empty. Point the row at step 5 for the cut and open, step 6 for the report, and name both skills' step numbers so the two ladders cannot be read as one. The landing was also described as unconditional. A fast-forward exists only while the queue branch still contains pr.base's tip, and the landing workflow updates the ref without force so GitHub refuses anything else — so release-please, a human's squash or another clone's branch:dev run closes the window and strands the queue PR by a second route. Reuse-before-cutting widens that window rather than narrowing it. State the condition, and put the recovery with the side that holds the credential rather than growing the drain a power the split just refused it. Refs #175
|
Re-work for review round 1 — head 1. The REFERENCE table pointed at the step the SKILL rules out — fixed
The reason the step matters is now stated under the table rather than left in the queue SKILL alone, since the table is what a hurrying agent reads:
2. Nothing stated when the fast-forward stops being possible — fixedTaken as asked: the skill states the condition and names whose job recovery is, and grows no power to rebase or land. Three places, because the promise appeared in three:
Not changedThe empty-check-list note needed no edit, as the review said. No change to Test disciplineProse-only round —
|
|
AI review — accepted (round 2 of 3, head Both round-1 blockers are genuinely fixed, and I checked the files rather than the re-work note. 1. The REFERENCE table pointed at a step the SKILL rules out — fixed, and the new numbers are correct
Verified against the actual ladders rather than taken on trust: in The sentence added under the table closes it properly:
The rationale now sits with the table an agent actually reads, not only in the queue SKILL body. The empty-queue stranding the finding described is no longer reachable by following the table. 2. The unbounded fast-forward promise — fixed, in all three places it appearedThe condition is stated, and stated precisely.
"Still contains Recovery is placed with the side holding the credential, and the drain grows no new power — only the obligation to report the queue PR's url every run so a stalled one is visible. That is the right resolution: it keeps the split of duties the whole mode is justified by. The same condition also reaches Regression check on the re-workThe round-2 delta touches three prose files and nothing else — Gate: Escalation checked, none applies: no One nit, not blocking
Accepted. |
The gate landed with #215 and binds every workflow added later: a PR workflow needs both the draft gate and ready_for_review in its trigger types, or the un-draft fires nothing and the check silently never runs. Until now that reasoning lived only in CLAUDE.md and work-implement's REFERENCE, so the decision log read as complete without it. Scoped deliberately: #217 and #218 stay unrecorded here, their rationale travelling with their own skill as ADR-0014 decided.
Summary
Adds the opt-in
work.queueBranchgate. Underbranch: worktreea drain at cap opens up to ten PRs intopr.base, each triggering the repo's full CI for work that all lands on one branch anyway. With the gate on, every issue PR targetsai/queue-<hash>instead, and the drain opens a singleai/queue-<hash>→pr.basePR to close the run — reusing one already open againstpr.baserather than cutting a second beside it.What the drain deliberately does not do is land that PR. No merge, no fast-forward, no bypass-capable credential on the machine running the loop. The target repo's own workflow mints its token in CI and fast-forwards the integration branch onto the queue PR's head once it is green. A fast-forward carries every commit across individually and unchanged, so release-please still writes one CHANGELOG entry per issue, and
allowed_merge_methodson the base branch is never touched.Off by default. Nothing in a drain can verify that landing workflow exists, and its absence fails silently and completely — every issue PR green, reviewed, and stranded on a branch nothing merges. So the config key is the repo's own assertion that the workflow is there, and a drain that cannot cut the branch or open the queue PR stops rather than falling back to
pr.base.The CI saving is documented, not promised: it exists only where workflow triggers are scoped to the integration branches (a
ci.ymlonpull_request.branches: [main, dev]fires nothing for a PR againstai/queue-*). On a public repo, where minutes are free, what is left is noise reduction.Inert under
branch:<name>, which opens no per-issue PR to group.Type of change
Checklist
pnpm verifypasses locallySKILL.mdupdated (description, examples)Test discipline
Mixed change, so
tddwas driven only where a test could reach. The schema key is the one testable seam the issue body yields ("Config gate. The mode is opt-in per repo … fail loudly rather than stranding work"), and it was driven red-green: the three new cases intest/schema.test.tsfailed against the unmodified schema (/work must NOT have additional properties) beforequeueBranchwas added, then passed. They pin the gate as optional, boolean, and profile-overlayable — absent staying valid is what makes it default to off, so no config written before the key silently acquires the mode.The rest —
SKILL.md,REFERENCE.md,DESIGN.md— is prose, which drivestddnot at all.pnpm verify: 181/181 green.pnpm skills:sync: no artifact drift (no frontmatter description changed).Related issues
Refs #175
Note for the reviewer
The issue's prerequisite has not landed. INF-43 — which owns the fast-forward workflow — is decided and specified but still In Progress, and it still lists this issue as blocked by it. What changed at 15:30, and what makes this implementable now rather than
blockedas at 15:06, is that the decision landed: the fast-forward replaces the merge commit, so theallowed_merge_methods: ["squash"]ruleset ondev(re-checked live for this run, still["squash"]) is no longer in the way.Shipping ahead of the workflow is safe because the gate defaults off — that is exactly what the body's config-gate requirement is for. What is not yet exercisable end-to-end is the mode itself: no repo can turn it on until INF-43 ships.