v1.12.0: the backlog gets a drain

Released 2026-08-18. One feature, three hours after the release before it: the issue queue had three ways in and no way out, and this is the way out.

What we were trying to fix

The kit’s issue lifecycle had three inlets and no outlet. merge-pr filed deferred work, auto-dev workers filed off-scope finds, and create-issue filed on request. The only way an issue had ever left the queue was somebody building it, so the backlog drained at exactly the speed of implementation, which is also the thing that filled it.

The numbers on this repo at the time made that concrete (#176): 95 issues opened, 65 closed, 30 open; zero ever closed as not planned; 22 of the 30 open ones citing the PR or issue that spawned them; and a backlog trend of 16, then 23, then 24, then 30. auto-dev already said the consequence out loud in its own documentation, that every PR you land tends to spawn more issues, and answered it by draining harder.

The awkward part was that no existing skill could host the outlet. create-issue explicitly disclaims managing existing issues, merge-pr explicitly disclaims standalone filing, and review-followups owns migration/report.json under a rule that it must never become a parallel list. Every published contract routed around the gap, so closing it meant a new skill rather than a rule bolted onto an old one.

What we decided

Ship triage-backlog: survey the open queue, exclude anything with work in flight, verify what a later PR already fixed, cluster the rest by root cause, and propose exactly one disposition per cluster (keep, sharpen, fold, rescope, close as done, close by decision), executing only what the owner confirms.

Two decisions inside that are the interesting ones.

First, closing by decision is a documented state, not a silent deletion. That doctrine already existed for migration reports, and it was imported wholesale rather than reinvented.

Second, triage-backlog is deliberately not hands-off, which makes it the one lifecycle skill that breaks the pattern. merge-pr can run unattended because CI objectively gates its irreversible act. Deciding that work will not be done is a judgement about intent, and no gate can stand in for the owner there. Two hard guards back that up: it never closes an issue with an open PR, an assignee or an auto-dev worker on it, and it never closes one as done without diff-level evidence. That split is now recorded as ADR 0005.

The release also gave the three inlets a shared filing bar (skills/_shared/filing-bar.md): a consequence someone hits, a named instance in the tree, or a commitment already made. The same question governs entry at an inlet and continued residence at the outlet, asked at two different times.

As for why this is its own release rather than part of v1.11.0, released the same morning: the two are three hours apart in the tag history and a full change of subject apart in intent. v1.11.0 is the batch that built the machine which fills the queue, the auto-dev fleet supervisor above all. v1.12.0 is the single feature that landed immediately afterwards, and release-please cuts a version per merged release PR, so the feature that arrives after one has merged gets its own minor whether or not it wants one. That the accident produced a clean pairing (fill, then drain) is luck, but it reads better than a merged entry would have.

What got cut

Three things, all recorded on the issue.

A filing ceiling on auto-dev was designed and rejected. Gating filings on backlog pressure would force an owner decision onto a hands-off path, colliding with that skill’s own autonomy contract; suppressing filings outright would trade a visible problem for an invisible one. What shipped instead was a report of backlog pressure at the existing every-five-merges re-survey, which tells the owner without stopping the fleet.

The recap redesign stayed out. The “scope complete” line went into merge-pr Step 8 as one bullet on the existing recap, deliberately not as a rework, because that was #175’s job.

boundary-trigger-eval.json was left alone. Its runner is hardcoded to the implement-issue and merge-pr pair, so the new close boundaries ride as negatives in the new skill’s own eval set rather than half-migrating a harness.

What bit us

The lineage bug, which is the reason the backlog looked worse than it was.

The file and subsystem search is the only query that can find an issue’s root cause, and it ran --state open at both inlets. An ancestor closed by the very PR being triaged was therefore invisible, so its unfinished remainder was filed as a sibling rather than recognised as the same job. The trail runs #93, then #166 which fixed it, then #172, a second issue for the same unfinished job that the closed ancestor would have absorbed had anything been able to see it. The search now spans all states, reopens an incomplete ancestor instead of re-filing, and stops at two ancestors deep, on the reasoning that a root which failed twice is mis-scoped rather than under-filed.

Worth noting that #176’s own body describes that trail as three rows for one job. It is two, and the error is instructive: even the issue arguing that the queue double-counts its own lineage miscounted a lineage.

One more, less flattering: 15 of 19 golden suites could not be run on the author’s own Windows checkout at the time, so CI was the only thing that settled this release. One of the three causes, tests/followups writing fixed /tmp paths, was fixed in v1.12.1 the next day (#160).