Repository navigation
fix(labels): strip status labels from closed issues and pull requests (#738) - #744
Merged
Merged
Conversation
A status label says what is happening to an item now, so leaving it on a closed issue or a merged pull request makes the board count finished work as in flight: on 2026-10-22 that was 11 closed issues and 43 closed pull requests. `label-hygiene.mjs` lists closed items once per status label (the `labels=` filter is an AND), removes only the five status labels in `STALE_LABELS`, and reads the repository back afterwards so the run reports what it actually achieved instead of trusting its own writes. `bug`, `area:*`, `risk:*`, `hold`, `needs-*` and `status:done` are records and are never touched; a dry run is the default and `--apply` is what writes. The workflow runs it daily and on `workflow_dispatch` with issues: write.
Collaborator
Author
|
Verdict: PASS at b4c4ff2 The script does what the issue asks and is safe to run unattended: it removes only labels from a closed set, it never touches What I checked in the code:
Ran: (tip Red, two cuts (each restored): Not run: the live Non-blocking:
|
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Written-by: Dev2
Fixes #738
Tip SHA: b4c4ff2
Reproduce
A status label says what is happening to an item now, but nothing removed one when the item closed: on 2026-10-22 the board counted 11 closed issues (
agent-working/agent-pr) and 43 closed pull requests (agent-pr) as in flight, and #718 closed while still carryingagent-working. Triage cleaned those 12 by hand; this replaces the hand cleanup with a run.The reproduction is a query, not a scenario:
GET /repos/ThinkFlowLab/FlyBot/issues?state=closed&labels=agent-prreturned 43 merged pull requests on that morning. The same live query is what the new script runs — seeWhat I ran.What changed
.github/scripts/label-hygiene.mjs(new):STALE_LABELS = ['agent-working', 'agent-pr', 'agent-ready', 'agent-task', 'awaiting-approval'], a closed set.planItem()keeps only items whosestateis notopenand removes only labels from that set;plan()dedupes by number, because the item with two status labels is returned by two queries.labels=filter is an AND:labels=a,breturns items carrying both, so one query for five labels would find almost nothing. Five queries, dedupe, one removal call per (item, label).--applyis what writes. Read back after writing: it re-runs the same queries and printsafter: N item(s), and a non-zeroafter:returnsok: false/ exit 1 — the run reports what it achieved instead of trusting its own DELETE calls.main({ gh, env, repo, apply })takes theghrunner andapplyas parameters, the waymerge-gate.mjsdoes, so the whole run is testable without network or credentials..github/workflows/label-hygiene.yml(new):schedule'30 19 * * *'(03:30 Asia/Shanghai, a quiet hour) plusworkflow_dispatch;permissions: contents: read, issues: write, pull-requests: write; pinnedactions/checkoutandactions/setup-node; runsnode .github/scripts/label-hygiene.mjs --apply..github/scripts/label-hygiene.test.mjs(new): 8 cases over a fakeghwhose fixtures are real API pages — dry run writes nothing, a closed issue loses exactly its status labels and keepsbug/risk:T1/area:*, an open issue is untouched, a merged PR losesagent-pr, an item with only kept labels produces no removal call at all, a second run on a clean repository reports 0,plan()dedupes and only counts closed items, and the keep-list is asserted as a closed set so widening the blast radius needs a test change.docs/autonomous-dev.md: one paragraph next to theaudit-mergespatrol, saying what this removes, what it never touches, and whyholdstays out.bug,area:*,risk:*,hold,needs-*andstatus:doneare records and are never removed. No changelog fragment: nothing users can see changes (same as #697).What I ran
At this tip
b4c4ff2c06d2a06e319f2a55f3fd7e5f5bed092e(base7af7c948, 4 files, +236/−0):node --test .github/scripts/*.test.mjs→ 212 tests, 212 pass, 0 fail. Baseline without the new file in the same checkout: 204/204 — this diff only adds tests.node --test .github/scripts/label-hygiene.test.mjs→ 8/8.main()driven with an injectedghoverapi.github.com):label-hygiene: before: 47 item(s) / 47 label(s) not open— 44 closed pull requests and 3 closed issues (Multi-pane layout overflows at narrow window widths: Details panel clipped, blank strip between panes #705bug+agent-working, Bind a PR's review PASS to its tip SHA (agent-merge gate) #215 and Docs say there is no merge queue, but main requires one — and a queue merge does start the push run #222agent-task), each listed with the label it would lose. The runner threw on anyDELETE, so this is a real read-only pass over the same pages the workflow will read.on: schedule, workflow_dispatch,cron: 30 19 * * *,permissions: {contents: read, issues: write, pull-requests: write}, pinned actions match the other workflows.arch-docs: OK (4 changed file(s));changelog.mjs check→ 19 fragments well formed, unchanged.Red without the source change
closed: trueinplanItem(drop thestate !== 'open'guard) → 7/8, the case asserting "an open issue is not touched" fails.STALE_LABELSfilter inplanItem(remove every label) → 3/8: dry run, close-keeps-records, merged PR, second run and the kept-labels case all fail.if (!apply) continueguard (a dry run writes) → 7/8, the dry-run case fails.Not run
mainyet, so there is no run id, no cron tick and nogh workflow runto point at. The first scheduled tick is 03:30 Asia/Shanghai after merge;workflow_dispatchcan fire it earlier.actionlintis not installed on this box; the YAML parse and the comparison against the existing workflows stand in for it.npm run verifyas a whole and the JVM-freepackagejob — untouched paths, no root workspace installed here.ghbinary is not on this box, sorealGhwas not executed; the API path was exercised throughfetchwith the same URLs.Risk
T3:
.github/**is T3 in.github/agent/risk-rules.json, and the diff matches itschild_processpattern. Greentests-okat the tip plus a PASS at this tip, then the queue — no approval click needed (user decision 2026-10-22).What the write can do: nothing but
DELETE /issues/<n>/labels/<name>for a label inSTALE_LABELS, on an item whosestateis notopen. The set is closed and asserted by a test, so a new label cannot start being removed by accident;holdis not in it in either direction, and the job holdsissues: writeonly — nocontents, noactions, no secrets. The read-back is what I would trust: if a removal silently fails, the run goes red instead of reporting success.Two judgement calls worth a reviewer's eye:
agent-taskandawaiting-approvalare in the set.awaiting-approvalis retired (#709 dropped its definition), so removing a leftover costs nothing;agent-taskis controller-managed per AGENTS.md but.github/scripts/controller.mjsnever adds or removes it (its sets areagent-pr,agent-ready,agent-working,risk:T*), so removing it on closed items creates no fight with the poll. If either should stay on a closed item, delete it from the one array and the closed-set test fails until it is described.Scheduling risk: GitHub delays scheduled runs (2.5–6 h observed here, #629), so the cleanup happens hours after a close, not at it. That is the trade for not putting a write step on every merge; the enqueue guard and the PR closing path are untouched.