Sibling of #238, which covers the send-back half. This one covers the close half, and corrects a claim #238 makes.
The observation
Two of the six ai:design rows drained on 2026-08-09 were not design questions and were not send-backs either — they were close-candidates wearing the wrong label:
Both producer notes said, in nearly identical words, "this PR has no remaining content and wants closing" and gave as the reason for using the design lane: "I cannot close a PR (gh pr close is forbidden — closing is a human decision), so routing it here."
That reason does not distinguish a design question from anything else. It is true of every close-candidate ever filed. Both rows then sat labelled for a week, parked outside every AI actor's queue, waiting for a human to be told what the producer had already worked out.
Correcting #238
#238 argues these two should still stop for the human because "closing is terminal." That conflates two different things and is wrong on this point.
ai:close-candidate is not closing. It is the flag state the machine is explicitly allowed to set — the standing rule is that the cron opens PRs and raises ai:close-candidate, and the human closes. The consuming transition is /ncc plus human_close, and that human step is untouched by anything proposed here.
So moving a design row to ai:close-candidate skips no human decision at all. It corrects the lane, and the row lands in the queue whose command is actually built to read it.
What to change
When /ndd's read lands on misroute whose correct state was ai:close-candidate, the command should relabel it to ai:close-candidate itself and report that it did, rather than presenting a design question for a human to convert by hand.
Two requirements on the relabel, both learned from these rows:
1. Carry the verified finding as the close-candidate reason. /ncc's job is to check the producer's stated reason against the issue and the code. If /ndd has already done a content comparison against main, that comparison is the reason to record — otherwise /ncc re-derives from scratch what was just established, and the round trip costs more than the label saved.
2. The reason must be a content comparison, never the merge experiment. Both notes offered the same evidence: merge origin/main in, resolve every conflict to main's side, diff the merged tree against origin/main, report byte-for-byte empty. That returns empty for any PR whose changes all conflict, because resolving to main's side is discarding the branch. On #112 the same note also stated a real difference from main one sentence later, so both statements cannot describe the same measurement — and the direct comparison found three real differences, all of them main ahead of the branch. A close-candidate reason built on that check is not checkable.
Do not let the relabel swallow what the PR was for
#112's close nearly orphaned something. Its linked issue #75 tracks three mutation-coverage rows; two landed on main via sibling changes, and the third — the missing-name address(0) passthrough — is pinned by no test on main and was dropped from the branch on purpose, because pinning it would entrench behaviour rain.flare#45 wants changed, via the still-open rain.flare#153.
So "the PR is superseded" and "the issue is resolved" are separate findings, and the second does not follow from the first. The relabel should state the linked issue's disposition explicitly rather than leaving it implied — covered by open PR is not closeable is the standing rule and it applies here in reverse.
Upstream fix, same as #238's
flag-design accepts any prose reason. Give it a typed reason discriminant and refuse "this PR has no remaining content" / "I cannot perform action X" at the point of flagging, naming ai:close-candidate as the correct transition. Then neither of these rows enters the design lane at all, and /ndd never needs the relabel path for them.
Sibling of #238, which covers the send-back half. This one covers the close half, and corrects a claim #238 makes.
The observation
Two of the six
ai:designrows drained on 2026-08-09 were not design questions and were not send-backs either — they were close-candidates wearing the wrong label:Both producer notes said, in nearly identical words, "this PR has no remaining content and wants closing" and gave as the reason for using the design lane: "I cannot close a PR (gh pr close is forbidden — closing is a human decision), so routing it here."
That reason does not distinguish a design question from anything else. It is true of every close-candidate ever filed. Both rows then sat labelled for a week, parked outside every AI actor's queue, waiting for a human to be told what the producer had already worked out.
Correcting #238
#238 argues these two should still stop for the human because "closing is terminal." That conflates two different things and is wrong on this point.
ai:close-candidateis not closing. It is the flag state the machine is explicitly allowed to set — the standing rule is that the cron opens PRs and raisesai:close-candidate, and the human closes. The consuming transition is/nccplushuman_close, and that human step is untouched by anything proposed here.So moving a design row to
ai:close-candidateskips no human decision at all. It corrects the lane, and the row lands in the queue whose command is actually built to read it.What to change
When
/ndd's read lands on misroute whose correct state wasai:close-candidate, the command should relabel it toai:close-candidateitself and report that it did, rather than presenting a design question for a human to convert by hand.Two requirements on the relabel, both learned from these rows:
1. Carry the verified finding as the close-candidate reason.
/ncc's job is to check the producer's stated reason against the issue and the code. If/nddhas already done a content comparison against main, that comparison is the reason to record — otherwise/nccre-derives from scratch what was just established, and the round trip costs more than the label saved.2. The reason must be a content comparison, never the merge experiment. Both notes offered the same evidence: merge
origin/mainin, resolve every conflict to main's side, diff the merged tree againstorigin/main, report byte-for-byte empty. That returns empty for any PR whose changes all conflict, because resolving to main's side is discarding the branch. On #112 the same note also stated a real difference from main one sentence later, so both statements cannot describe the same measurement — and the direct comparison found three real differences, all of them main ahead of the branch. A close-candidate reason built on that check is not checkable.Do not let the relabel swallow what the PR was for
#112's close nearly orphaned something. Its linked issue #75 tracks three mutation-coverage rows; two landed on main via sibling changes, and the third — the missing-name
address(0)passthrough — is pinned by no test on main and was dropped from the branch on purpose, because pinning it would entrench behaviour rain.flare#45 wants changed, via the still-open rain.flare#153.So "the PR is superseded" and "the issue is resolved" are separate findings, and the second does not follow from the first. The relabel should state the linked issue's disposition explicitly rather than leaving it implied —
covered by open PR is not closeableis the standing rule and it applies here in reverse.Upstream fix, same as #238's
flag-designaccepts any prose reason. Give it a typed reason discriminant and refuse "this PR has no remaining content" / "I cannot perform action X" at the point of flagging, namingai:close-candidateas the correct transition. Then neither of these rows enters the design lane at all, and/nddnever needs the relabel path for them.