Skip to content

scripts/triage.py: what is blocking the issue and PR queues, in one command - #820

Merged
lmoresi merged 2 commits into
developmentfrom
tooling/triage-script
Oct 6, 2026
Merged

lmoresi merged 2 commits into
developmentfrom
tooling/triage-script

Conversation

@lmoresi

@lmoresi lmoresi commented Oct 6, 2026 •

Copy link
Copy Markdown
Member

Read-only. Calls gh and git, writes nothing.

$ python scripts/triage.py
=== triage against origin/development (1391e4aa) ===

ISSUES  118 open   101 unlabelled
  by author: lmoresi 108, gthyagi 6, jcgraciosa 1, bknight1 1, NengLu 1, juliusdeblaaij 1
  30 referenced by a merged PR -- PROBE these, do not close on the reference
    of which 2 have a merged PR claiming to close them

PULL REQUESTS  32 open
  MERGEABLE    CI green   20   closes 640, 747, 793 (hashes dropped: see note)
  MERGEABLE    CI red      5   closes 783, 784, 788
  CONFLICTING  CI green    3   closes 791
  CONFLICTING  CI red      2   closes nothing
  CONFLICTING  CI none     2   closes nothing
  25 of 32 carry no Closes line

That last line is the whole diagnosis of why merging stopped resolving anything.

The part that cost ten days

RED PR FAILURES  (checked against development)
  #800  behind 58  -- MERGE development IN, 2 failure(s) renamed away there:
      phantom  tests/test_1060_nitsche_freeslip.py::...::test_nitsche_normal_velocity_zero
      phantom  tests/test_1070_free_surface_plume.py::test_freesurface_strong_constraint_beats_penalty
      new      tests/test_1103_navier_stokes_velocity_transport.py::test_each_velocity_history...
               ^ the PR's own new file -- a real failure
  #780  behind 27
      live     tests/test_1056_navier_stokes_supg_api.py::test_tau_shapes_construct_and_step
               ^ still on the base -- a real failure

#785, #789, #795 and #800 had been red for ten days on two tests development
had already renamed, with byte-identical numbers across all four. Nothing said
so anywhere.

The three-way classification is not cosmetic. The first version of this script
reported #800's own new test_1103_navier_stokes_velocity_transport.py as a
phantom — which would have sent someone to merge development and find nothing
fixed. A file absent from the base arrived with the PR, so its failure is the
PR's own; only a test missing from a file that is still there has been renamed
away.

Why it reports probe candidates and not a ranking

Fifteen open issues turned out to be already fixed. The ones that closed cleanly
all had a regression test naming the issue in its docstring; the ones that did
not were merged PRs referencing a neighbouring defect. A reference is a reason
to look, never a verdict, so the tool says "probe these" and stops.

gh's GraphQL endpoint returns a bare 502 often enough that the calls retry —
it did so three times while this output was being captured.

Underworld development team with AI support from Claude Code


The issue numbers in the sample output above have had their hashes removed. They are this tool's report, not a declaration — and a closing verb followed by a hash and a number is exactly the shape GitHub's linked-issue parser looks for, so left as written this PR would have asked to close seven issues it has nothing to do with. Whether a code fence protects you is unresolved: closingIssuesReferences comes back empty through gh even for a PR whose body plainly declares one, so that field does not settle it. The script now strips fenced blocks before its own scan, which is what it should have done from the start — it was reporting itself as the biggest issue-closer in the queue.

A 133-issue backlog and a 32-PR queue turned out to be two different failures
wearing the same clothes, and neither was visible without cross-referencing by
hand. This does the cross-reference in one command, read-only.

Three things it reports, each because it was expensive to learn the first time.

Issues referenced by a merged PR, as candidates to PROBE and never as a
verdict. Fifteen open issues were already fixed on development; the ones that
closed cleanly all had a regression test naming the issue, and the ones that
did not were references to a NEIGHBOURING defect. So the tool says "probe
these" and refuses to rank them any further.

PRs bucketed by mergeable x CI, with what each bucket closes, and the count
that carry no Closes line at all -- 25 of 32 today, which is the whole reason
a merge stopped resolving anything.

And the one that cost ten days: a red PR whose failing tests no longer exist
on the base. Four PRs sat red on two tests that development had already
renamed, with byte-identical numbers across all four. The tool pulls the
failing names out of the CI log and classifies each: phantom (the file is on
the base, the test is not -- merge the base and it clears), new (the file came
with the PR, so the failure is its own and real), or live. That distinction is
not cosmetic -- the first version reported #800's own new
test_1103_navier_stokes_velocity_transport.py as a phantom, which would have
sent someone to merge the base and find nothing fixed.

gh's GraphQL endpoint returns a bare 502 often enough that the calls retry;
a tool meant to be run several times a session cannot treat one as fatal.

Underworld development team with AI support from Claude Code
Copilot AI balanced review requested due to automatic review settings October 6, 2026 03:12
lmoresi added a commit that referenced this pull request Oct 6, 2026
The script is in #820; this is the line that sends a session to it.

Underworld development team with AI support from Claude Code

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copilot review overview

🟡 Changes recommended

CI status handling, failure aggregation, and stale-base handling can produce misleading triage results.

Review effort: Balanced
Findings: 3 Medium severity · 1 Low severity

Open (4)
What changed in this PR

Adds a queue-triage utility for identifying issue backlog and pull-request blockers.

Changes:

  • Summarizes open issues, merged-PR references, and missing closure links.
  • Groups pull requests by mergeability and CI state.
  • Classifies failed tests as live, new, or renamed on development.
File Description
scripts/​triage.py Implements GitHub queue and CI-failure triage.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread scripts/triage.py
Comment on lines +84 to +89
outcomes = [c.get("conclusion") for c in (pr.get("statusCheckRollup") or [])]
if "FAILURE" in outcomes:
return "red"
if "SUCCESS" in outcomes:
return "green"
return "none"
Comment thread scripts/triage.py
Comment on lines +98 to +114
for check in pr.get("statusCheckRollup") or []:
if check.get("conclusion") != "FAILURE":
continue
url = check.get("detailsUrl") or ""
job = url.rsplit("/job/", 1)[-1]
if not job.isdigit():
continue
log = sh("gh", "run", "view", "--log-failed", "--job", job, check=False)
found = set()
for line in log.splitlines():
# The CI log prefixes each line with the step name and a timestamp.
hit = FAILED.search(line[line.find("FAILED"):]) if "FAILED" in line else None
if hit:
found.add((hit.group(1), hit.group(2)))
if found:
return found
return set()
Comment thread scripts/triage.py
if not body:
return "new"
leaf = name.split("::")[-1]
return "live" if f"def {leaf}" in body else "phantom"
Comment thread scripts/triage.py
fixed and renamed. Their failures named tests that no longer existed. That
is the ``phantom`` column, and it is the one worth looking at first.

Read-only: it calls ``gh`` and ``git`` and writes nothing. Run it often; the
This script reports which issues a PR closes by scanning its body. Its own PR
pastes its own output, which contains the line "closes #640, #747, #793" as
sample text -- so it reported itself as closing seven issues it has nothing to
do with.

Fenced code blocks are now stripped before the scan. Whether GitHub's own
linked-issue parser makes the same mistake is unresolved:
`closingIssuesReferences` comes back empty through `gh` even for a PR whose
body plainly says "Closes #793", so that field does not answer it either way.
The sample output in the PR description has had its hashes removed so the
question cannot arise.

Underworld development team with AI support from Claude Code
@lmoresi
lmoresi merged commit 629f443 into development Oct 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants