Skip to content
Productivity
Command

/triage-prs

Sync open GitHub PRs into the github-pr tracker, rank the untriaged ones for merge priority, and spawn /review-contribution sessions for the ones worth a deep look

From plugin
nimbalyst
1.7k49 skills3 agents49 commands
Install
$ npx -y skills add nimbalyst/nimbalyst --agent claude-code

How it fires

How this command gets triggered: by you, by Claude, or both.

  • Fires itselfClaude auto-loads it when your prompt matches the work.
  • You can call itInvoke it directly when you want it.
  • Slash command/triage-prs

Context preview

What this command does when you run it.

Sync open GitHub PRs into the github-pr tracker, rank the untriaged ones for merge priority, and spawn /review-contribution sessions for the ones worth a deep look

Command definition

triage-prs.md
name: triage-prs
description: Sync open GitHub PRs into the github-pr tracker, rank the untriaged ones for merge priority, and spawn /review-contribution sessions for the ones worth a deep look

Triage Open GitHub PRs

Keep the `github-pr` tracker in sync with GitHub, then help me decide two things for every PR that hasn't been looked at yet:

1. **Priority** — how important is this to merge, relative to everything else in the queue? (`low` / `medium` / `high` / `critical`) 2. **Is it worth a deep review right now**, or is it blocked (CI failing, conflicts, needs contributor changes) / clearly out of scope / too stale to bother with yet?

For the ones worth a deep review, let me pick a subset and spawn isolated Nimbalyst sessions that run `/review-contribution <PR#>` against each — same dispatch pattern as `/review-multiple-contributions`, throttled to N at a time.

This command does **not** do the deep review itself. It's triage: fast, metadata-only (`gh pr view` / `gh pr checks`, no diffs), meant to answer "what order should I work through this queue in," not "is this PR safe to merge." That's `/review-contribution`'s job.

Usage

`/triage-prs [using <model>] [max <N> at a time]`

Both are optional and only affect the spawn step in Step 6:

  • `using <model>` — model for spawned `/review-contribution` sessions. Same

resolution table as `/review-multiple-contributions` (`opus`, `sonnet`, `haiku`, `gpt-5`/`codex`, or a passthrough `provider:model` string). If omitted, spawned sessions inherit this session's model (`inheritModel: true`).

  • `max <N> at a time` — concurrency for spawning, default **3**, clamp to

`[1, 10]`.

Steps

1. Fetch open PRs from GitHub

gh pr list \
  --state open \
  --limit 200 \
  --json number,title,url,author,headRefName,baseRefName,isDraft,createdAt,updatedAt

(`authorAssociation` is not a valid `--json` field on `gh pr list`/`gh pr view`; get it per-PR via `gh api repos/{owner}/{repo}/pulls/<pr> --jq .author_association` when needed in Step 3.)

Filter out PRs where `isDraft` is `true`.

2. Sync the tracker

Pull only the **non-terminal** items — the working set this command acts on. `complete`/`rejected` items are already closed out and both sync steps below explicitly leave them alone, so dumping every historical PR (the 500 cap is mostly terminal) into context is pure token waste:

tracker_list({
  type: "github-pr",
  where: [
    { field: "status", op: "!=", value: "complete" },
    { field: "status", op: "!=", value: "rejected" }
  ],
  limit: 500
})

Dedupe on `prNumber`, not URL (URLs can shift if a fork is renamed).

Edge case: a PR that was previously merged/closed (terminal) and then **reopened** on GitHub won't be in this filtered set, so it'll be re-created as a fresh `backlog` item rather than reconciled in place — that's acceptable (a reopened closed PR genuinely warrants fresh triage). Don't widen the filter to catch it; the full-scan cost isn't worth that rare case.

  • **Already tracked** → leave `status`, `priority`, and `notes` alone for now

(they may already have maintainer-set values) — it gets re-evaluated in Step 4 only if `status` is still `backlog`.

  • **Not tracked** → create it:
tracker_create({
  type: "github-pr",
  title: "<PR title>",
  fields: {
    prUrl: { url: "<pr url>", label: "#<number>" },
    prNumber: <number>,
    author: "<author.login>",
    headBranch: "<headRefName>",
    baseBranch: "<baseRefName>",
    status: "backlog"
  }
})

**Close the loop on merged/closed PRs.** For each tracked item whose `status` is not yet `complete` or `rejected` and whose `prNumber` is **not** in the open-PR list from Step 1, check what happened on GitHub:

gh pr view <prNumber> --json state,mergedAt,closedAt,title

(If an old item is missing `prNumber`, find it with `gh pr list --state all --search "<distinctive title words>" --json number,state,mergedAt,title`.)

Then update the tracker so the board reflects reality:

  • `state: MERGED` → `tracker_update` with `status: "complete"` and a note like

`"Merged on GitHub <mergedAt date>."` (prepend it; keep any useful prior review note in parentheses). Backfill `prUrl`/`prNumber` if they were missing.

  • `state: CLOSED` (not merged) → `status: "rejected"` with

`"Closed on GitHub <closedAt date> without merge."`

  • Still `OPEN` (e.g. it was just past the `--limit`, or re-opened) → leave it

alone.

Surface every item updated this way in the report (Step 5).

This command only **creates** tracker items during sync — it never deletes. Review-verdict status transitions (`inspecting` → `needs-review`/`safe`) are owned by `/review-contribution` and the maintainer. The **only** `status` writes this command makes are the merged/closed reconciliations above, which mirror GitHub facts rather than making a judgment call.

3. Gather triage signals for untriaged PRs

"Untriaged" = tracker items with `status: backlog` (new ones from Step 2, plus any left over from a previous run that never got reviewed).

For each untriaged PR, in parallel:

gh pr view <pr> --json number,title,author,authorAssociation,body,additions,deletions,changedFiles,files,labels,createdAt,updatedAt,mergeable,mergeStateStatus,reviewDecision,isCrossRepository
gh pr checks <pr>

Do **not** run `gh pr diff` or read file contents — this is a metadata-only pass. Derive, per PR:

  • **Age** — days since `createdAt`, and days since `updatedAt` (staleness).
  • **Size** — XS (<20 lines) / S (<100) / M (<400) / L (<1000) / XL (1000+),

from `additions + deletions`.

  • **Sensitive-area touch** — does `files[].path` match auth, sync/collab,

IPC/main-process, persistence/DB, or other areas called out as high-blast-radius in `CLAUDE.md`? Flag if so — informs priority and whether it needs `/review-contribution`'s deeper analysis rather than a quick maintainer glance.

  • **CI status** — passing / failing / pending / no checks, from `gh pr
Read more
Ships withnimbalyst

Nimbalyst - The open-source visual workspace for Claude Code, Codex, and OpenCode. Run multiple coding agents in parallel, edit their work visually in markdown, mockups, and diagrams, and track tasks. Free, MIT-licensed desktop app for macOS, Windows, Linux, with mobile companion for iOS and Android.

Get the whole plugin

Other commands on nimbalyst.