From b8d5bb51e296f1cceb87f9269907d7826a7034c7 Mon Sep 17 00:00:00 2001 From: lmoresi Date: Wed, 7 Oct 2026 08:48:48 +1100 Subject: [PATCH 1/2] A label for the gap between merged and released GitHub closes a linked issue when its PR reaches the DEFAULT branch. Here that is `main`, while the work merges to `development`, and merges to `main` are infrequent. So between a merge and a release an issue is fixed and still open, and nothing in the repository says so. The mechanism itself is sound: across the project's life 139 declared closes produced 5 that slipped. It is release-gated, not broken, and it was worth establishing that before adding anything -- the backlog was not caused by it. `fixed-in-development` is applied at merge and the release closes everything carrying it in one pass. Closing at merge stays a reasonable choice for a defect nobody outside is waiting on; it is only misleading for one somebody is. scripts/triage.py now lists every issue a merged PR declared that is neither closed nor labelled, and names three outcomes rather than two. The third is the reason the list is candidates and not fixes: #611 was credited to #656 and its hang is handled by a `--deselect` in scripts/test.sh at the very rank count the issue reports. The tool finds exactly that one today. Underworld development team with AI support from Claude Code --- docs/developer/guides/adversarial-review.md | 12 ++++++ scripts/triage.py | 46 ++++++++++++++++++++- 2 files changed, 57 insertions(+), 1 deletion(-) diff --git a/docs/developer/guides/adversarial-review.md b/docs/developer/guides/adversarial-review.md index f05386aa..1d281f48 100644 --- a/docs/developer/guides/adversarial-review.md +++ b/docs/developer/guides/adversarial-review.md @@ -119,6 +119,18 @@ on the PR with its evidence: | **Already fixed** | it was fixed earlier and nobody closed it | a probe run against `development`, pasted into the issue as the closing comment, showing the behaviour the issue describes no longer happens | | **Still live** | the branch passes through it and leaves it | one line saying which part still reproduces, so the next session does not re-derive it | +**`fixed-in-development` carries an issue from merge to release.** GitHub +closes a linked issue when its PR reaches the *default* branch, which here is +`main`, while the work merges to `development`. Merges to `main` are +infrequent, so between the two an issue is fixed and still open and nothing +says so. Across the project's life 139 declared closes produced only 5 that +slipped, so the mechanism works — it just works at release time. Label the +issue at merge, and the release closes everything carrying the label in one +pass. Closing it at merge instead is a reasonable choice for a defect nobody +outside is waiting on, but it reads as a lie to anyone running the release. +`scripts/triage.py` lists every issue a merged PR declared that is neither +closed nor labelled. + **Refusing to close is a verdict, not a gap.** A branch that touches the territory of an issue and does not fix it says so, in a line. The failure this replaces is silence: the fix lands, the issue reads as familiar to anyone who diff --git a/scripts/triage.py b/scripts/triage.py index 3f091de6..e9483bba 100755 --- a/scripts/triage.py +++ b/scripts/triage.py @@ -44,6 +44,11 @@ def prose(body): FAILED = re.compile(r"^FAILED\s+(\S+?)::(\S+?)(?:\[|\s|$)") BASE = "development" +#: Merges to `main` are infrequent, so GitHub's own issue-closing (which fires +#: only on a default-branch merge) lags reality by a release. An issue whose fix +#: is on `development` carries this label until then: closing it would be a lie +#: to anyone running the release, and leaving it bare loses the fact entirely. +FIXED_LABEL = "fixed-in-development" def sh(*args, check=True, tries=1): @@ -83,7 +88,7 @@ def issues(): def prs(state="open"): fields = ("number,title,mergeable,additions,deletions,changedFiles," - "statusCheckRollup,body,headRefName,author,updatedAt") + "statusCheckRollup,body,headRefName,baseRefName,author,updatedAt") args = ["pr", "list", "--limit", "400", "--json", fields] if state != "open": args += ["--state", state] @@ -219,6 +224,45 @@ def main(): no_closes = [p for p in opn if not CLOSES.search(prose(p["body"]))] print(f" {len(no_closes)} of {len(opn)} carry no Closes line") + # ---- the gap between "merged" and "released" --------------------------- + # GitHub closes a linked issue when the PR reaches the DEFAULT branch, and + # this project's default branch is `main` while the work merges to + # `development`. Across the project's life 139 declared closes produced 5 + # that slipped, so the mechanism does work -- but it works at release time, + # and releases are rare. Between a merge and a release an issue is fixed + # and still open, and nothing says so unless somebody labels it. + labels = {i["number"]: {l["name"] for l in i["labels"]} for i in iss} + declared = {} + for p in merged: + if p.get("baseRefName") not in (None, BASE): + continue + for n in {int(m) for m in CLOSES.findall(prose(p["body"]))}: + if n in open_numbers: + declared.setdefault(n, []).append(p["number"]) + limbo = {n: v for n, v in declared.items() if FIXED_LABEL not in labels.get(n, ())} + print(f"\nFIXED BUT STILL OPEN (a merged PR declared it; `{FIXED_LABEL}` not applied)") + if not limbo: + print(f" none -- every declared close is either closed or labelled") + for n in sorted(limbo): + src = ", ".join(f"#{p}" for p in sorted(limbo[n])) + print(f" #{n:<5} declared by {src:<14} {titles[n][:58]}") + if limbo: + print("\n Probe each. Three outcomes, and the third is why this is a list") + print(" of candidates rather than a list of fixes:") + print(f" fixed and you want it off the board -> gh issue close ") + print(f" fixed, waiting on a release -> gh issue edit " + f"--add-label {FIXED_LABEL}") + print(" NOT fixed -- the PR addressed a neighbour, or papered over it") + print(" -> leave open, say which part is live") + print(" #611 is the standing example of the third: #656 was credited with") + print(" closing it, and the hang is handled by a --deselect in scripts/test.sh") + print(" at the very rank count the issue reports.") + carrying = [n for n, ls in labels.items() if FIXED_LABEL in ls] + if carrying: + print(f"\n {len(carrying)} issue(s) already carry `{FIXED_LABEL}` and close at " + f"the next release:") + print(" " + " ".join(f"#{n}" for n in sorted(carrying))) + # ---- the one that cost ten days --------------------------------------- if args.no_logs: return From 2e63777861641741583e52a3b12f40cfdd716bb8 Mon Sep 17 00:00:00 2001 From: lmoresi Date: Wed, 7 Oct 2026 19:02:10 +1100 Subject: [PATCH 2/2] fixed-in-PR, not fixed-in-development: the fix is often not merged at all The label was named for the gap between a merge and a release, and that framing made the tool scan merged PRs only. It found one issue. Scanning open PRs as well finds four more, and those are the ones that matter: #785 carries the fix for #783 and #784 and cannot merge, because its base branch has no PR of its own. On the day the label was added, four of the five issues it applied to were in that state. So the label says the fix is written, and nothing more. Which PR, and whether it has merged, lives in the issue and in this tool's output, where it can be kept true -- a label cannot be. The merge-to-release half still holds: GitHub closes a linked issue on a DEFAULT-branch merge and that is `main`, reached infrequently here. 139 declared closes across the project's life produced 5 that slipped, so the mechanism is release-gated rather than broken, and it was worth establishing that before adding anything. Underworld development team with AI support from Claude Code --- docs/developer/guides/adversarial-review.md | 28 +++++++----- scripts/triage.py | 50 +++++++++++++-------- 2 files changed, 48 insertions(+), 30 deletions(-) diff --git a/docs/developer/guides/adversarial-review.md b/docs/developer/guides/adversarial-review.md index 1d281f48..b6115d86 100644 --- a/docs/developer/guides/adversarial-review.md +++ b/docs/developer/guides/adversarial-review.md @@ -119,17 +119,23 @@ on the PR with its evidence: | **Already fixed** | it was fixed earlier and nobody closed it | a probe run against `development`, pasted into the issue as the closing comment, showing the behaviour the issue describes no longer happens | | **Still live** | the branch passes through it and leaves it | one line saying which part still reproduces, so the next session does not re-derive it | -**`fixed-in-development` carries an issue from merge to release.** GitHub -closes a linked issue when its PR reaches the *default* branch, which here is -`main`, while the work merges to `development`. Merges to `main` are -infrequent, so between the two an issue is fixed and still open and nothing -says so. Across the project's life 139 declared closes produced only 5 that -slipped, so the mechanism works — it just works at release time. Label the -issue at merge, and the release closes everything carrying the label in one -pass. Closing it at merge instead is a reasonable choice for a defect nobody -outside is waiting on, but it reads as a lie to anyone running the release. -`scripts/triage.py` lists every issue a merged PR declared that is neither -closed nor labelled. +**`fixed-in-PR` says the fix is written.** Two situations share the label and +neither is visible without it. A PR merged to `development` has not closed its +issue, because GitHub closes on a *default*-branch merge and that is `main`, +which this project reaches infrequently; across its life 139 declared closes +produced only 5 that slipped, so the mechanism works — it works at release +time. And a PR that has not merged at all can hold the only fix there is: +#785 carries #783 and #784 and cannot merge, its base branch having no PR of +its own. On the day the label was added, four of the five issues it applied to +were in that second state, not the first. + +The label does not record which situation, or which PR. That belongs in the +issue and in `scripts/triage.py`, which lists every issue a PR declares that +is neither closed nor labelled, and marks each PR merged or open. A label +cannot be kept true; a line in the issue can. + +Closing at merge instead of labelling is a reasonable choice for a defect +nobody outside is waiting on. It reads as a lie to anyone running the release. **Refusing to close is a verdict, not a gap.** A branch that touches the territory of an issue and does not fix it says so, in a line. The failure this diff --git a/scripts/triage.py b/scripts/triage.py index e9483bba..e1307cb6 100755 --- a/scripts/triage.py +++ b/scripts/triage.py @@ -44,11 +44,17 @@ def prose(body): FAILED = re.compile(r"^FAILED\s+(\S+?)::(\S+?)(?:\[|\s|$)") BASE = "development" -#: Merges to `main` are infrequent, so GitHub's own issue-closing (which fires -#: only on a default-branch merge) lags reality by a release. An issue whose fix -#: is on `development` carries this label until then: closing it would be a lie -#: to anyone running the release, and leaving it bare loses the fact entirely. -FIXED_LABEL = "fixed-in-development" +#: An issue whose fix lives in a PR carries this until the fix is released. +#: Two situations share it, and both are invisible without it. A PR merged to +#: `development` has not closed its issue, because GitHub closes on a +#: DEFAULT-branch merge and that is `main`, which this project reaches +#: infrequently. And a PR that has not merged at all may hold the only fix +#: there is -- #785 carries the fix for #783 and #784 and cannot merge, +#: because its base branch has no PR of its own. +#: +#: The label does not say which of the two, or which PR. That belongs in the +#: issue and in this tool's output, where it can be kept true; a label cannot. +FIXED_LABEL = "fixed-in-PR" def sh(*args, check=True, tries=1): @@ -224,13 +230,13 @@ def main(): no_closes = [p for p in opn if not CLOSES.search(prose(p["body"]))] print(f" {len(no_closes)} of {len(opn)} carry no Closes line") - # ---- the gap between "merged" and "released" --------------------------- - # GitHub closes a linked issue when the PR reaches the DEFAULT branch, and - # this project's default branch is `main` while the work merges to - # `development`. Across the project's life 139 declared closes produced 5 - # that slipped, so the mechanism does work -- but it works at release time, - # and releases are rare. Between a merge and a release an issue is fixed - # and still open, and nothing says so unless somebody labels it. + # ---- issues whose fix is already written ------------------------------ + # Two ways an open issue can already be fixed, and neither is visible + # anywhere. A PR merged to `development` has not closed it, because GitHub + # closes on a DEFAULT-branch merge and that is `main` (139 declared closes + # across the project's life, 5 slipped -- release-gated, not broken). And + # an UNMERGED PR can hold the only fix: #785 carries #783 and #784 and + # cannot merge, its base branch having no PR of its own. labels = {i["number"]: {l["name"] for l in i["labels"]} for i in iss} declared = {} for p in merged: @@ -238,14 +244,18 @@ def main(): continue for n in {int(m) for m in CLOSES.findall(prose(p["body"]))}: if n in open_numbers: - declared.setdefault(n, []).append(p["number"]) + declared.setdefault(n, []).append((p["number"], "merged")) + for p in opn: + for n in {int(m) for m in CLOSES.findall(prose(p["body"]))}: + if n in open_numbers: + declared.setdefault(n, []).append((p["number"], "open")) limbo = {n: v for n, v in declared.items() if FIXED_LABEL not in labels.get(n, ())} - print(f"\nFIXED BUT STILL OPEN (a merged PR declared it; `{FIXED_LABEL}` not applied)") + print(f"\nFIX ALREADY WRITTEN (a PR declares it; `{FIXED_LABEL}` not applied)") if not limbo: - print(f" none -- every declared close is either closed or labelled") + print(" none -- every declared fix is closed or labelled") for n in sorted(limbo): - src = ", ".join(f"#{p}" for p in sorted(limbo[n])) - print(f" #{n:<5} declared by {src:<14} {titles[n][:58]}") + src = ", ".join(f"#{p} ({st})" for p, st in sorted(limbo[n])) + print(f" #{n:<5} {src:<26} {titles[n][:52]}") if limbo: print("\n Probe each. Three outcomes, and the third is why this is a list") print(" of candidates rather than a list of fixes:") @@ -257,10 +267,12 @@ def main(): print(" #611 is the standing example of the third: #656 was credited with") print(" closing it, and the hang is handled by a --deselect in scripts/test.sh") print(" at the very rank count the issue reports.") + print(" A PR marked (open) is the commoner case here -- the fix is written") + print(" and cannot land. Label the issue; the work is on the PR.") carrying = [n for n, ls in labels.items() if FIXED_LABEL in ls] if carrying: - print(f"\n {len(carrying)} issue(s) already carry `{FIXED_LABEL}` and close at " - f"the next release:") + print(f"\n {len(carrying)} issue(s) carry `{FIXED_LABEL}`; the release closes " + f"the ones whose PR has merged:") print(" " + " ".join(f"#{n}" for n in sorted(carrying))) # ---- the one that cost ten days ---------------------------------------