Repository navigation
fix(ci): strip queue labels when an issue closes - #12865
Conversation
A hand-closed issue kept agent-ready, status: ready and admission-deferred, so queue counts and listings still read it as waiting. work-unblock's close run now removes those labels from the closed issue itself, reading it fresh: only the labels present come off, agent-ready last, and a 404 counts as removed. An issue open again by the time the run reads it, or a post-merge fix issue (which keeps agent-ready for its reopen on a repeat failure), keeps them. The list is report-outcome's ADMISSION_LABELS, now exported, and the lane-PR hand-off to triage removes its labels through the same helper, so both paths share one source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s-on-close # Conflicts: # .github/scripts/work-unblock/label-unblocked.mjs
An App close starts no work-unblock close run, so the issue kept its queue labels. The two places the App closes an issue now strip them right after the close, with the token each already holds: report-outcome's close route (already-done, report-delivered) and work-unblock's split-parent close. stripQueueLabels and the list move to report-outcome.mjs, which label-unblocked already imports, so every path shares one helper; report-outcome's API errors now carry their HTTP status so a 404 counts as removed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GitHub closes an issue when a pull request the lanes App merged lands, and no close run follows a bot's close, so such an issue kept its queue labels. work-unblock's existing sweep, which runs after every work-resolve-issue run and hourly, already walks each issue a listed human or a merge closed in its window; it now strips their queue labels there too, through the shared helper, with the same exemptions. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
On a sweep pass, each closed issue was read three times: by the close check, by the label strip, and by the split-parent pass. The close check (readClose) now caches the issue it read beside its verdict; the strip takes that read, and the split-parent pass shares the same cache, so a merge-closed issue is read once per run. The close-event path keeps its own fresh read, since that job can run after a reopen. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
|
claude-security-review reviewed Security review: PR #12865, incremental from
|
|
claude-review reviewed Code review: incremental, f7bbe7a → 911b590Scope: I read every hunk of the 2 files this push changed: Findings: none. Prior finding resolvedThe advisory finding at discussion_r4458535389 said the close route's label DELETEs were not resent after a 5xx. This push fixes that:
TestsBoth 502 tests now expect Notes (not counted)
|
report-outcome's close route and closeDoneParent stripped the queue labels after their PATCH, so a failed label DELETE (not resent on a 5xx) left a closed issue with agent-ready that no sweep reads. Both now strip first; a failed removal fails the run with the issue still open and agent-ready on it, for work-unblock's sweep. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…s-on-close Compose #12710's verify-and-close flow for a finished split parent with this branch's strip-before-close rule: - report-outcome finishStop keeps main's verifyClose argument; its verified-done close goes through the same close route, so the queue labels come off on a fresh read before the PATCH. A new test covers that order and a 502 on a label DELETE leaving the issue open. - label-unblocked closeSplitParents and closeSplitParent take both the shared close-check cache and main's mirror; main keeps the stripClosed kick pass and passes closes and mirror together. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A label DELETE is idempotent and a 404 already counts as removed, so the close route sends its removals as safeToResend. One transient 5xx no longer fails a finished stop and sends the issue back for another lane run; a removal that keeps failing still fails the run before the close. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No related issue: hand-closed issues keep queue labels (ledger L105, e.g. #12583)
Summary
An issue closed by hand kept
agent-ready,status: readyandadmission-deferred. Example: #12583, closed with "Fixed by #12585". In a sample of 24 closed issues that still carryagent-ready, 19 were closed by a listed human and 5 by the lanes App. Before this change, the only code that removed both labels was work-unblock's lane-PR hand-off. That hand-off runs only on an open issue whose lane PR merged withRefs:or closed unmerged.Fix
work-unblock.ymlalready runs onissues: [closed]. Itslabel-unblocked.mjsclose path now strips the queue labels from the closed issue itself (stripClosed), so no new workflow, job or token mint is added.It reads the issue fresh and removes only the queue labels present, with
agent-readylast. A 404 counts as removed.An issue that is open again by the time the run reads it keeps its labels.
A post-merge fix issue keeps its labels, matched by
github-actions[bot]as author plus a<!-- post-merge-fix check=... -->first line.post-merge-demotion-alert.ymlreopens such an issue withagent-readykept, and the sweep relabels it.The close strip writes nothing to the Work status field, because none of that field's states fits a closed issue.
Where the lanes App closes an issue itself, it strips the labels right after the close, with the token it already holds. Its close starts no close run, so the close path above never sees it.
report-outcome.mjs: the close route (already-done,report-delivered), reading the labels from the close's own answer.label-unblocked.mjscloseDoneParent: the split-parent close.One helper:
stripQueueLabelsand its list (ADMISSION_LABELS) live inreport-outcome.mjs, whichlabel-unblocked.mjsalready imports. The close path, both App close sites and the lane-PR hand-off all use it. The hand-off now also removesadmission-deferredwhen present, and sends no DELETE for astatus: readyit does not carry.report-outcome.mjs's API errors now carry their HTTP status, so a 404 counts as removed there too.Left as they are:
post-merge-demotion-alert.yml's close of a fix issue, which usesGITHUB_TOKENand must keepagent-readyfor the reopen.monitor.mjsandfile-findings.mjs. They already dropagent-ready, and admission never gives those issuesstatus: ready.Token: unchanged. The close run already gets a lanes App token from the token broker (
request-lane-token, lanework-unblock, activitylabel-unblocked), limited to this repository with the grant resolved fromdocs/conventions/pr-pipeline.yaml. The step only runs when the closer and the re-runner are listed humans.Trigger safety: an App label removal fires
issues: unlabeled. The workflows that listen for it do nothing on a closed issue:intake-reenter.ymlrequiresgithub.event.issue.state == 'open'.security-outside-actor.ymlacts on an unlabel only forsecurity: outside-actor.work-resolve-issue.ymltriggers onlabeledonly.pr-merge.yml's unlabeled trigger ispull_request_target.A label DELETE never reopens an issue.
Merge closes: GitHub closes an issue when a pull request the lanes App merged lands, and no close run follows a bot's close. work-unblock's existing sweep already walks each issue a listed human or a merge closed in its window, after every work-resolve-issue run and hourly. It now strips their queue labels there with
stripClosed, so this adds no new poller.Verification
node --testonlabel-unblocked.test.mjsandreport-outcome.test.mjs: 681 of 681 pass. The new tests cover:admission-deferredincluded;agent-readylast;agent-readylast, with no write when none is left and a 404 counted as removed;agent-readylast, with no write for one without them or for a close by neither a listed human nor a merge.actionlintonwork-unblock.yml: clean, apart from the existingconcurrency.queuekey, which the local actionlint 1.7.12 does not know.runner-policy.mjs --repository-visibility public: passed.scripts/check-affected.sh --base origin/main --no-fetch: 10 passed, 5 skipped, after merging main (the first run failedstale-base-overlapon perf(lanes): configure work-unblock's stranded re-fire backoff per cause and shorten it for transient causes #12804, now merged in).Found
LOOKBACK_HOURS(3) ago, which is outside the sweep's window, so the one-off cleanup below covers them.Related
.github/workflows/post-merge-demotion-alert.yml(the fix-issue reopen this keeps working)🤖 Generated with Claude Code