Description
Closing a priced issue as not planned — invalid, duplicate, wontfix — writes a NO_CLOSING_PULL_REQUEST row into the moderation queue, exactly as closing it as completed does. The fold cannot tell the two apart, because it never asks.
issuesQuery in src/lib/github/client.ts:680 selects state and not stateReason, and stateReason appears nowhere in src/:
$ git grep -n "stateReason\|state_reason\|NOT_PLANNED" -- 'src/**/*.ts'
(no matches)
So repository-fold.ts:392 sees only issue.state !== "CLOSED", and at line 411 records the closure whenever no merged closing pull request was selected and the evidence window is reachable. A deliberate "this was never going to be done" closure produces the same row as an accidental hand-closure.
Live example. daedalus issue 632 was closed not_planned with the invalid label at 2026-09-07T18:31:55Z. The fold that ran 20:32:57–20:35:49 local wrote its row anyway:
issue_number | kind | created_at
--------------+-------------------------+-------------------------------
632 | NO_CLOSING_PULL_REQUEST | 2026-09-07 20:35:49.553307+02
That row cannot be resolved by anyone, which is #174: the queue routes it for correction and correction is the one thing its audience cannot perform. Unlike a rejected settlement, there is nothing to correct — the issue was correctly closed and correctly has no pull request.
What makes this hard to notice is that the fold only materialises issues carrying an opening catalog label — 565 issues in the database, none without one. So an unpriced issue closed by hand never enters the fold and leaves no trace. Issues 191 and 215 were both closed not planned and left nothing, and both were unpriced; that coincidence is why this repository's own working notes record "closing not_planned does not write the row" as though the state reason were the discriminator. It is not. The discriminator is whether the issue was ever priced.
Expected Behavior
An issue closed as not planned records no unwritable closure. Nothing was left undone, so there is nothing to route to a moderator.
An issue closed as completed with no merged closing pull request still records one, because that is a real gap: work was declared done and the ledger cannot say by whom or for how much.
Reproduction Steps
- Take an open issue carrying an opening catalog label.
- Close it as not planned —
gh issue close <n> --reason "not planned".
- Let reconciliation run.
- Observe a
NO_CLOSING_PULL_REQUEST row for it in unwritable_closures, indistinguishable from one written for a completed closure.
Environment / Context
main at f7a8e2c. Related: #174 is why the resulting row is permanent — the moderation queue offers a correction its audience cannot perform, and for this kind there is not even a correction to make.
Discovered During
Auditing why the daedalus rejected-evidence queue held 204 rows against 203 granted corrections, on 2026-09-07. The uncovered row was 632, which an automated correction pass had correctly declined to touch because it is not a rejected settlement.
Suggested Fix
Unverified. Add stateReason to issuesQuery, carry it through the snapshot, and skip the closure record when it is NOT_PLANNED. The fold already has the shape for it — the record is gated on evidenceWindowReachable at the same site, so this is one more condition rather than a new path.
Worth deciding in the same change what happens to rows already written for not-planned closures, since the fold recomputes fully: if the row is simply no longer produced, existing ones disappear on the next pass without a migration.
Description
Closing a priced issue as not planned — invalid, duplicate, wontfix — writes a
NO_CLOSING_PULL_REQUESTrow into the moderation queue, exactly as closing it as completed does. The fold cannot tell the two apart, because it never asks.issuesQueryinsrc/lib/github/client.ts:680selectsstateand notstateReason, andstateReasonappears nowhere insrc/:So
repository-fold.ts:392sees onlyissue.state !== "CLOSED", and at line 411 records the closure whenever no merged closing pull request was selected and the evidence window is reachable. A deliberate "this was never going to be done" closure produces the same row as an accidental hand-closure.Live example. daedalus issue 632 was closed
not_plannedwith theinvalidlabel at 2026-09-07T18:31:55Z. The fold that ran 20:32:57–20:35:49 local wrote its row anyway:That row cannot be resolved by anyone, which is #174: the queue routes it for correction and correction is the one thing its audience cannot perform. Unlike a rejected settlement, there is nothing to correct — the issue was correctly closed and correctly has no pull request.
What makes this hard to notice is that the fold only materialises issues carrying an opening catalog label — 565 issues in the database, none without one. So an unpriced issue closed by hand never enters the fold and leaves no trace. Issues 191 and 215 were both closed not planned and left nothing, and both were unpriced; that coincidence is why this repository's own working notes record "closing
not_planneddoes not write the row" as though the state reason were the discriminator. It is not. The discriminator is whether the issue was ever priced.Expected Behavior
An issue closed as not planned records no unwritable closure. Nothing was left undone, so there is nothing to route to a moderator.
An issue closed as completed with no merged closing pull request still records one, because that is a real gap: work was declared done and the ledger cannot say by whom or for how much.
Reproduction Steps
gh issue close <n> --reason "not planned".NO_CLOSING_PULL_REQUESTrow for it inunwritable_closures, indistinguishable from one written for a completed closure.Environment / Context
mainatf7a8e2c. Related: #174 is why the resulting row is permanent — the moderation queue offers a correction its audience cannot perform, and for this kind there is not even a correction to make.Discovered During
Auditing why the daedalus rejected-evidence queue held 204 rows against 203 granted corrections, on 2026-09-07. The uncovered row was 632, which an automated correction pass had correctly declined to touch because it is not a rejected settlement.
Suggested Fix
Unverified. Add
stateReasontoissuesQuery, carry it through the snapshot, and skip the closure record when it isNOT_PLANNED. The fold already has the shape for it — the record is gated onevidenceWindowReachableat the same site, so this is one more condition rather than a new path.Worth deciding in the same change what happens to rows already written for not-planned closures, since the fold recomputes fully: if the row is simply no longer produced, existing ones disappear on the next pass without a migration.