Skip to content

Add PR triage skills: status labeller + Mon/Wed/Fri digest - #1090

Open
daimon-pymclabs wants to merge 8 commits into
mainfrom
feat/pr-triage-digest
Open

Add PR triage skills: status labeller + Mon/Wed/Fri digest#1090
daimon-pymclabs wants to merge 8 commits into
mainfrom
feat/pr-triage-digest

Conversation

@daimon-pymclabs

Copy link
Copy Markdown

What

A two-layer, agent-assisted PR triage system to help work down the review backlog without removing human judgment from reviews. This is the Tier 1 ("recommend, don't act") slice: the digest is read-only, and the only GitHub writes are a tightly-scoped label namespace behind an explicit --apply flag.

Design: labels, not states

Every open PR carries several orthogonal, non-exclusive facts (it can be conflicting and have changes requested and be stale at once). So the model is a set of independent labels, plus a single derived next-action computed via an explicit precedence order used only for grouping.

Layer 1 — shared deterministic core

.agents/skills/_shared/pr_triage/labels.py is the single source of truth. It fetches open PRs via gh (forcing the lazily-computed mergeable field), computes per-PR facts — lifecycle, conflict, ci, review, decision-needed, risk, idle-band, author-class — and derives one next_action. No LLM judgment; it is a pure function of the GitHub API. All tunables (maintainer/labs handles, idle thresholds, precedence order, managed labels) live at the top of the file.

Layer 2 — two thin skills sharing that core

They call the same function, so they can never disagree.

  • pr-status-labeller reconciles a status:* label namespace on GitHub (conflicting, ci-failing, waiting-on-author, ready-for-review, aging, stale). Dry-run by default; --apply to write. Touches only status:* — never review:*, major, needs:maintainer-decision, or any human-owned label. Drafts stay out of reviewer-facing statuses.
  • pr-digest produces a read-only Mon/Wed/Fri digest. It surfaces exactly one hard decision per run, a short clean-review queue (external contributors first), and collapses everything else under a "no action needed" divider. It recommends only; it never writes to GitHub.

Why one hard item per digest

The digest is shaped to counter decision paralysis, not just capacity: it recommends a default to veto rather than asking an open question, frames each hard item as a small reversible step, and names the cost of not deciding. Surfacing one deep-focus decision per run (rather than the full high-risk list) keeps the queue from being deferred wholesale.

Idle thresholds

Aging nudge at 30d, stale warning at 90d. The digest recomputes state live rather than trusting the labeller's last run, so a missed labeller run degrades GitHub visibility but never corrupts the digest.

Triggering

The logic is trigger-agnostic: an automation can run either skill on a schedule, or a maintainer can run them locally. Only the posting step differs.

Checks run locally

ruff check (clean), ruff format (clean), codespell (clean), pymarkdown against the repo config (clean), py_compile, and both scripts smoke-tested against the live repo (42 open PRs classify without error; labeller dry-run produces a sane add/remove diff).

Not in this PR

Mechanical execution (auto-nudges, branch syncs) and any agent-authored reviews. Those are later tiers, deliberately gated behind this read-only slice earning trust first.

🤖 Generated with Claude Code

Introduces a two-layer, agent-assisted PR triage system to help work down
the review backlog without removing human judgment from reviews.

Layer 1 - shared deterministic core (.agents/skills/_shared/pr_triage/labels.py):
single source of truth that fetches open PRs via `gh`, computes orthogonal
per-PR facts (lifecycle, conflict, CI, review, decision-needed, risk,
idle-band, author-class), and derives one next-action per PR via an explicit,
tunable precedence order. No LLM judgment; pure function of the GitHub API.

Layer 2 - two thin skills that consume the same core so they can never disagree:
- pr-status-labeller: reconciles a `status:*` label namespace on GitHub
  (conflicting, ci-failing, waiting-on-author, ready-for-review, aging, stale).
  Dry-run by default; touches only status:* labels, never human-owned ones.
- pr-digest: read-only Mon/Wed/Fri digest for Discord. Surfaces exactly one
  hard decision per run (anti-choice-overload), a short clean-review queue,
  and collapses everything else as "no action needed". Recommends only;
  never writes to GitHub.

Idle thresholds: aging at 30d, stale at 90d. The digest recomputes state live
rather than trusting the labeller's last run, so a missed labeller run degrades
GitHub visibility but never corrupts the digest.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@cursor

cursor Bot commented Jul 27, 2026

Copy link
Copy Markdown

PR Summary

Medium Risk
reconcile_labels.py --apply mutates open PR labels on GitHub; misclassification could mis-route the backlog, though writes are limited to status:* and dry-run is the default.

Overview
Introduces a Tier 1 PR triage stack under .agents/skills/: a shared gh-driven core classifies every open PR into orthogonal facts plus one precedence-ordered next_action, with no LLM in the classification path.

pr-status-labeller reconciles only the six status:* labels on GitHub (dry-run by default; --apply creates labels and patches PRs). pr-digest builds a Mon/Wed/Fri Discord markdown skeleton via build_digest.py—one hard decision, quick-review queue (externals first), merge-ready list, and an FYI block—leaving {{HARD_ITEM_FRAMING}} for light agent prose per format.md.

The core deliberately avoids updatedAt for idle time (so label edits do not reset aging), treats failed per-PR gh pr view as detail_incomplete (withholds or carries idle labels), and handles CI rollup edge cases (e.g. CANCELLED, STALE) so digest and labeller stay aligned.

Reviewed by Cursor Bugbot for commit b3645e9. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread .agents/skills/pr-digest/scripts/build_digest.py
Comment thread .agents/skills/_shared/pr_triage/labels.py
Comment thread .agents/skills/_shared/pr_triage/labels.py
@codecov

codecov Bot commented Jul 27, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 95.98%. Comparing base (79c0a87) to head (b3645e9).
⚠️ Report is 2 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #1090   +/-   ##
=======================================
  Coverage   95.98%   95.98%           
=======================================
  Files         104      104           
  Lines       16277    16277           
  Branches      912      912           
=======================================
  Hits        15623    15623           
  Misses        488      488           
  Partials      166      166           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

- ruff E741/E731: rename ambiguous `l` loop vars, replace lambda-assign with def
- Make NEXT_ACTION_ORDER load-bearing: CLI table now sorts by its rank, not alphabetically
- Guard emitted status labels against STATUS_LABELS; tie LABEL_DEFS to it so they can't drift
- Exclude hard-item PRs (lead + queued) from quick reviews to stop double-counting

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@daimon-pymclabs

Copy link
Copy Markdown
Author

Pushed 1058868 addressing all three Bugbot findings and the failing prek (ruff) check:

  • Precedence list never applied — the CLI table now sorts by NEXT_ACTION_RANK derived from NEXT_ACTION_ORDER instead of alphabetically, so the list is genuinely the single place to tune display precedence. (The if/elif in _next_action still encodes the branch logic, but reordering the list now actually reorders output.)
  • Managed labels set unused_status_labels_for now asserts its emitted set stays within STATUS_LABELS, and reconcile_labels.py asserts set(LABEL_DEFS) == STATUS_LABELS. Editing one without the other now trips at import instead of silently drifting.
  • Hard items listed as quick reviewsreviews now excludes the hard lead PR and every other_hard number, so major/decision PRs no longer double-count in the quick-review list and header.
  • prek — fixed E741 (llab) in labels.py/reconcile_labels.py and E731 (lambda→def by) in build_digest.py; ruff check and ruff format both clean locally.

Comment thread .agents/skills/_shared/pr_triage/labels.py
Comment thread .agents/skills/_shared/pr_triage/labels.py
_next_action now walks NEXT_ACTION_ORDER and returns the first action whose
predicate holds (true first-match-wins), so the precedence list is genuinely
the single source of truth for both classification and the CLI/consumer sort.
Verified behavior-identical to the old if/elif chain across all 576 input
combinations. Adds a drift guard asserting the predicate map matches the order
list, and corrects the NEXT_ACTION_ORDER docstring.

Findings 1 (quick-review double-count) and 3 (STATUS_LABELS dead constant) were
already resolved in the prior BugBot pass (1058868); left unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@daimon-pymclabs

Copy link
Copy Markdown
Author

Addressed the BugBot findings (commit e26d69f):

Finding 2 (MEDIUM — NEXT_ACTION_ORDER not the source of truth) — FIXED.
The CLI table already sorted by NEXT_ACTION_RANK (derived from NEXT_ACTION_ORDER), but _next_action still hardcoded an independent if/elif chain, so reordering the list would not change classification precedence. _next_action now maps each action to a predicate and walks NEXT_ACTION_ORDER, returning the first action whose predicate holds — genuine first-match-wins, with ready-for-review as the always-true fallback that must stay last. Added a drift-guard assert that the predicate keys equal NEXT_ACTION_ORDER, and corrected the docstring. Verified behavior-identical to the old chain across all 576 input combinations (decision_needed × lifecycle × idle_band × review × conflict × ci); 0 mismatches. NEXT_ACTION_ORDER is now the single source of truth for both classification and display ordering.

Finding 1 (MEDIUM — quick-review double-count) — already resolved (no change needed).
The current code already excludes hard-item PRs from the quick-review list: hard_numbers is built from the lead hard item plus other_hard, reviews filters on f["number"] not in hard_numbers, and the header count uses len(reviews). This was fixed in the prior pass (1058868); nothing left to do.

Finding 3 (LOW — STATUS_LABELS dead constant) — already resolved (no change needed).
STATUS_LABELS is no longer dead: _status_labels_for asserts its emitted set is a subset of STATUS_LABELS (drift guard), and reconcile_labels.py asserts set(LABEL_DEFS) == core.STATUS_LABELS, tying both emission and label creation to the one managed set. Single source of truth is enforced. Also fixed in 1058868.

No unit tests exist for these scripts; validated via the exhaustive equivalence check above plus --help/py_compile on all three scripts. No lines exceed the 88-char limit.

Comment thread .agents/skills/_shared/pr_triage/labels.py Outdated
BugBot (High): idle_days/idle_band were derived from GitHub's `updatedAt`,
which a label-only `gh pr edit` bumps. So each `--apply` run of the labeller
reset the idle clock, and the next run saw a "fresh" PR and stripped the
`status:aging`/`status:stale` label it had just added; the aging/stale state
could never persist across scheduled applies, and the live idle figures the
digest reads were corrupted.

Fix, in the shared core (labels.py) so digest and labeller stay consistent:
- Add `_last_activity(pr)`: the max of PR creation, commit push times, and
  comment timestamps. None of these are moved by a label edit, so idleness is
  now stable across labeller runs.
- `idle_days` is computed from `_last_activity(pr)` instead of `updatedAt`.
- Fetch `comments` in the bulk `pr list`, and fetch `commits` per-PR in the
  existing `gh pr view` poke loop. `commits` cannot go in the bulk list: each
  commit carries an authors connection, which pushes a 200-PR listing past
  GitHub's 500k GraphQL node limit. The per-PR fetch piggy-backs on the same
  view call already used to resolve lazy `mergeable`, so it adds no extra call.

CI (prek): the `ruff-format` hook was red on this branch. Applied ruff-format
to labels.py; `prek run` on all PR-touched files is now green.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@daimon-pymclabs

Copy link
Copy Markdown
Author

Addressed the BugBot idle-timer finding and the failing prek check (commit c62eb0d).

BugBot (High): label writes reset the idle timer. FIXED.
Confirmed real. idle_days/idle_band were computed from GitHub's updatedAt, which a label-only gh pr edit bumps. So every --apply run of the labeller reset the idle clock; the next reconcile then saw a "fresh" PR and stripped the status:aging/status:stale label it had just written, meaning aging/stale state could never persist and the live idle figures the digest reads were corrupted.

Fix lives in the shared core (_shared/pr_triage/labels.py) so the digest and the labeller read the exact same number:

  • New _last_activity(pr): the most recent of PR creation, commit push times (committedDate), and comment timestamps (createdAt). None of these are moved by a label edit, so idleness is now stable across labeller runs.
  • idle_days derives from _last_activity(pr) instead of updatedAt.
  • comments is fetched in the bulk pr list; commits is fetched per-PR inside the existing gh pr view poke loop. commits cannot go in the bulk listing: each commit carries an authors connection, which pushes a 200-PR query past GitHub's 500k GraphQL node limit (verified: it errors). The per-PR fetch piggy-backs on the same gh pr view already used to resolve lazy mergeable, so it adds no extra API call.

Verified end to end against the live repo: labels.py, reconcile_labels.py (dry-run), and build_digest.py all run clean, and PR #1090 now reports idle 0d from its latest commit rather than a label bump.

prek CI. FIXED.
The failing hook was ruff-format (not lint). Ran the hooks locally; ruff-format reformatted labels.py. prek run across every file this PR touches is now green.

Comment thread .agents/skills/_shared/pr_triage/labels.py
Comment thread .agents/skills/_shared/pr_triage/labels.py
@drbenvincent drbenvincent added review:high High-impact change requiring thorough human review needs:maintainer-decision Maintainer direction needed before review can conclude labels Jul 31, 2026
@drbenvincent

Copy link
Copy Markdown
Collaborator

Automated triage

Recommendation: review:highmaintainer decision needed.

Why:

  • Adds two new triage automation skills (.agents/skills/pr-digest/, .agents/skills/pr-status-labeller/) and a shared core — this is a workflow and agent-tooling change, not a CausalPy library change.
  • A shared core module (labels.py) that would read/write GitHub labels via gh — this is an operational concern with permissions and automation implications.
  • The PR description explicitly frames this as a "read-only" Tier 1 slice, but the status-labeller script writes labels to GitHub.
  • All CI checks pass.

Review focus:

  1. Decide whether PR triage automation belongs in this repo at all — the description calls for a maintainer to "earn trust" before later tiers are added.
  2. Verify the status:* label namespace does not conflict with existing label conventions or review workflows.
  3. Confirm the shared cores gh-based API calls handle authentication and rate-limiting gracefully in CI.

Confidence: medium — the PR adds non-trivial tooling that needs a maintainer to decide on direction before a code review can settle it.

- CI: ignore CANCELLED/SKIPPED/NEUTRAL (cancel-in-progress concurrency
  leaves superseded runs as CANCELLED, which was marking green PRs red)
- CI: treat STARTUP_FAILURE as a failure, not a green check
- Add a mergeability-unknown next_action so PRs whose mergeable never
  resolves are not advertised as clean review targets, plus a digest
  bucket so they don't silently vanish
- Count reviews in the idle calculation so a just-reviewed PR can't be
  labelled aging/stale

Co-Authored-By: Claude <noreply@anthropic.com>
@daimon-pymclabs

Copy link
Copy Markdown
Author

Addressed the 4 remaining BugBot findings in c62eb0d..HEAD:

  • Cancelled checks counted as failing_ci_state now filters CANCELLED/SKIPPED/NEUTRAL out of the rollup before evaluating. With cancel-in-progress concurrency, superseded runs linger as CANCELLED next to their replacement, which was marking green PRs status:ci-failing and routing them to mechanical.
  • Startup failure treated as greenSTARTUP_FAILURE added to the failure set.
  • Unknown mergeable marked ready — added a mergeability-unknown next_action (just above ready-for-review in NEXT_ACTION_ORDER) for the case where GitHub never resolves mergeable even after the per-PR gh pr view retry. Those PRs no longer get status:ready-for-review, and the digest gained a below-the-line bucket so they don't silently disappear.
  • Reviews ignored in idle calculationreviews is now fetched in the same per-PR gh pr view pass as commits (kept out of the bulk list for the node-budget reason already documented) and folded into _last_activity, so a just-reviewed PR can't immediately pick up status:aging/status:stale.

ruff check and ruff format --check are clean. @drbenvincent your triage comment recommends review:high / maintainer decision — that's still yours to call, nothing blocking from my side.

Comment thread .agents/skills/_shared/pr_triage/labels.py Outdated
The per-PR `gh pr view` was wrapped in contextlib.suppress, so a transient
failure left `commits`/`reviews` unset and classification carried on.
`_last_activity` then fell back to creation time plus issue comments, which
overstates idleness for any PR whose recent activity was pushes or reviews,
and `--apply` could write a wrong `status:aging`/`status:stale`.

Retry the view once, warn on stderr if it still fails, flag the PR
`detail_incomplete`, and withhold the idle-derived labels for it.
@daimon-pymclabs

Copy link
Copy Markdown
Author

Addressed the BugBot finding Silent fetch failure corrupts idle (high severity) in e676d44.

The per-PR gh pr view was wrapped in contextlib.suppress(CalledProcessError), so a transient failure left commits/reviews unset while classification carried on. _last_activity then fell back to createdAt plus issue comments, overstating idleness for any PR whose recent activity was pushes or reviews, and --apply could write a wrong status:aging/status:stale.

Now: retry the view once (these are usually rate-limit/network blips), warn on stderr if it still fails, flag the PR detail_incomplete (surfaced on PRFacts and in the JSON payload), and withhold the idle-derived labels for that PR. mergeable stuck at UNKNOWN already routes to the mergeability-unknown bucket and never gets status:ready-for-review, so that half of the finding was covered.

Comment thread .agents/skills/_shared/pr_triage/labels.py Outdated
Comment thread .agents/skills/_shared/pr_triage/labels.py
@read-the-docs-community

read-the-docs-community Bot commented Aug 3, 2026

Copy link
Copy Markdown

Two follow-ups to e676d44, both correct.

Withholding the idle labels was worse than the bug it fixed: the labeller
reconciles by set difference, so omitting `status:aging`/`status:stale`
reads as a remove, and one unlucky fetch would strip durable idle state.
Carry the existing idle labels through instead, so the reconcile is a no-op
for them: a failed fetch neither writes a new verdict nor erases the last
good one.

`_next_action` still routed drafts by `idle_band`, so the digest could call
a draft stale on the same number the labeller refused to trust. Introduce
`idle_band == "unknown"` as the single carrier of "do not act on this":
the draft buckets test by equality and now fall through to
`in-flight-draft`, and the label derivation branches on it too.
@daimon-pymclabs

Copy link
Copy Markdown
Author

Both follow-up findings were right, and the first one is a regression I introduced in e676d44. Fixed in 04834ea.

Incomplete fetch strips idle labels (high). Correct, and worse than the bug it was meant to fix. reconcile_labels.py diffs want against have, so a label omitted from status_labels is a label it removes: one unlucky gh pr view would have wiped correct status:aging/status:stale off a PR. _status_labels_for now takes the PR's existing status:* labels and, in the unknown band, carries the idle ones through unchanged. The reconcile becomes a no-op for them, so a failed fetch neither writes a new idle verdict nor erases the last good one. Only IDLE_LABELS carry over; a stale status:conflicting is still removed normally.

Draft action ignores incomplete idle (medium). Also correct: the labeller refused to trust the number while the digest happily bucketed a draft as stale on it. Rather than add a second detail_incomplete guard, the fix collapses both call sites onto one signal: _idle_band now returns unknown when the detail fetch failed. The draft predicates test the band by equality, so they no longer match and the draft falls through to in-flight-draft; label derivation branches on the same value. One source of truth for "do not act on this idle figure" instead of two places to keep in sync.

Verified across the matrix: healthy stale still labels stale; incomplete with an existing status:stale keeps it; incomplete with no idle label adds none; incomplete draft routes to in-flight-draft; incomplete does not resurrect status:conflicting.

Comment thread .agents/skills/_shared/pr_triage/labels.py
Comment thread .agents/skills/pr-digest/scripts/build_digest.py Outdated
Comment thread .agents/skills/pr-digest/scripts/build_digest.py Outdated
Three findings from the latest BugBot pass, all correct.

`_ci_state` let a `STALE` conclusion fall through to `green`: it was in
neither the ignored set nor the failure set. GitHub does not count a stale
required check as successful, so a PR could reach `ready-to-merge` on a check
GitHub gave up on. Unlike `CANCELLED`, a `STALE` check is not superseded by a
sibling run in the rollup, so it genuinely needs a re-run -- `red` is the
right verdict and routes it to `mechanical`.

The digest printed `idle_days` as fact in the hard-item line even when
`detail_incomplete` was set, i.e. the exact figure the core already refuses to
derive labels from. It now renders "idle unknown" there, and the review queue
sorts untrusted-idle PRs after the trusted ones in their author group instead
of interleaving them on a number nobody should act on.

`_fmt` interpolated raw PR titles between `[` and `]`, so a `[WIP]`/`[RFC]`
prefix closed the markdown link on the wrong character and corrupted the rest
of the line. Titles are now escaped.

Co-Authored-By: Claude <noreply@anthropic.com>
@daimon-pymclabs

Copy link
Copy Markdown
Author

BugBot pass addressed — b3645e9

Fixed (3)

  • _ci_state treated STALE as green (medium) — correct, and the worst of the three: STALE was in neither the ignored set nor the failure set, so it fell through to green and a PR could reach ready-to-merge on a check GitHub had given up on. Now failing. Worth saying why red and not pending: ready-to-merge only excludes ci == "red", so pending would not have closed the hole. And unlike CANCELLED — which is ignored precisely because cancel-in-progress leaves a superseded run next to the run that replaced it — a STALE check has no sibling success in the rollup, so it really does need a re-run. mechanical is the right bucket.
  • Digest printed untrusted idle_days (medium) — correct. The hard-item line rendered idle_days as fact even under detail_incomplete, which is the exact figure the core already refuses to derive labels from (idle_band == "unknown"). It now prints idle unknown. The review queue also sorts untrusted-idle PRs after trusted ones within their author-class group. Minor correction to the report: the sort is ascending by idle, so an inflated figure demoted a PR rather than overstating its urgency — the ordering was noisy, not urgency-inflating.
  • Brackets broke digest markdown links (medium) — correct. [WIP]/[RFC] prefixes are common here and the raw ] closed the link on the wrong character, corrupting the rest of the line. Titles are now escaped via a _md helper.

Rejected as already fixed (2) — both were filed against e676d44 and 04834ea (Make an incomplete fetch neutral, not destructive) landed the fixes before the comments posted. No code change needed:

  • "Incomplete fetch strips idle labels" (high) — _status_labels_for already takes the PR's existing status:* labels and, on idle_band == "unknown", carries existing & IDLE_LABELS through unchanged, making the set-difference reconcile a no-op for the idle labels. Verified: _status_labels_for({... "idle_band": "unknown" ...}, {"status:stale"}) returns ['status:stale', 'status:ready-for-review'] — the durable state is preserved, not stripped.
  • "Draft action ignores incomplete idle" (medium) — _idle_band already returns "unknown" when detail_incomplete, and the stale-draft/aging-draft predicates test the band by equality, so an incomplete draft matches neither and falls through. Verified: a draft with idle_band == "unknown" classifies as in-flight-draft.

All 9 earlier cursor[bot] threads on this PR are resolved. ruff check and ruff format clean on both touched files.

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, have a team admin enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit b3645e9. Configure here.

f["author_class"] != "external",
f["detail_incomplete"],
f["idle_days"],
),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Review queue starves old PRs

Medium Severity

The quick-review list sorts by ascending idle_days, so freshest PRs appear first. Longer-waiting external PRs sink as new ones arrive. The shared CLI table already ranks by descending idle, so the digest backlog order fights the triage goal of working down neglected reviews.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b3645e9. Configure here.

or (f["major"] and f["next_action"] == "ready-for-review")
)
and (hard is None or f["number"] != hard["number"])
]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Queued decisions out of order

Medium Severity

other_hard keeps gh pr list order (newest created first) instead of sorting by age_days like pick_hard_item. The “queued behind this one” line then shows the newest remaining hard items, not the next-oldest by cost-of-waiting.

Additional Locations (1)
Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit b3645e9. Configure here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs:maintainer-decision Maintainer direction needed before review can conclude review:high High-impact change requiring thorough human review

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants