Pre-Release PR Triage
jamiepine/voicebox
Sorts a backlog of open pull requests into must-merge, candidate, superseded and deferred, writes a triage doc and works the merge loop before a release.
Triages open GitHub issues and pull requests with the gh CLI, optionally merging ready PRs, closing resolved issues with evidence and assigning local priority and size estimates.
$ npx skills add trailofbits/skills --skill github-triage -a claude-codeProject install by default; add -g for ~/.claude/skills/.
$ gh skill install trailofbits/skills github-triage --agent claude-codeProject scope by default; add --scope user for a personal install. Needs GitHub CLI 2.90.0 or later (public preview).
$ git clone --depth 1 https://github.com/trailofbits/skills.git skills-src && mkdir -p .claude/skills && cp -r skills-src/plugins/github-triage/skills/github-triage .claude/skills/github-triage && rm -rf skills-srcUse ~/.claude/skills/ instead of .claude/skills for a personal install. The folder must contain SKILL.md.
Claude Code skills documentation · loads skills from .claude/skills/
Install the "github-triage" agent skill from https://github.com/trailofbits/skills/tree/main/plugins/github-triage/skills/github-triage into .claude/skills/github-triage/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "github-triage", then confirm the skill loads.Claude Code copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$skill-installer install https://github.com/trailofbits/skills/tree/main/plugins/github-triage/skills/github-triageType this inside Codex. $skill-installer <name> installs a curated skill from openai/skills. The installer writes to $CODEX_HOME/skills (default ~/.codex/skills). Restart Codex if the skill does not show up.
$ npx skills add trailofbits/skills --skill github-triage -a codexProject install goes to .agents/skills/; add -g for ~/.codex/skills/.
$ gh skill install trailofbits/skills github-triage --agent codexProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/trailofbits/skills.git skills-src && mkdir -p .agents/skills && cp -r skills-src/plugins/github-triage/skills/github-triage .agents/skills/github-triage && rm -rf skills-srcUse ~/.agents/skills/ instead of .agents/skills for a personal install.
Codex skills documentation · loads skills from .agents/skills/
Install the "github-triage" agent skill from https://github.com/trailofbits/skills/tree/main/plugins/github-triage/skills/github-triage into .agents/skills/github-triage/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "github-triage", then confirm the skill loads.Codex copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add trailofbits/skills --skill github-triage -a cursorProject install goes to .agents/skills/; add -g for ~/.cursor/skills/.
$ gh skill install trailofbits/skills github-triage --agent cursorProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/trailofbits/skills.git skills-src && mkdir -p .cursor/skills && cp -r skills-src/plugins/github-triage/skills/github-triage .cursor/skills/github-triage && rm -rf skills-srcUse ~/.cursor/skills/ instead of .cursor/skills for a personal install.
Cursor skills documentation · loads skills from .cursor/skills/, .agents/skills/, .claude/skills/, .codex/skills/
Install the "github-triage" agent skill from https://github.com/trailofbits/skills/tree/main/plugins/github-triage/skills/github-triage into .cursor/skills/github-triage/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "github-triage", then confirm the skill loads.Cursor copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gemini skills install https://github.com/trailofbits/skills.git --path plugins/github-triage/skills/github-triage--scope user (default) or --scope workspace; --path is the subfolder of the repo that holds the skill; --consent skips the security confirmation prompt.
$ npx skills add trailofbits/skills --skill github-triage -a gemini-cliProject install goes to .agents/skills/; add -g for ~/.gemini/skills/.
$ gh skill install trailofbits/skills github-triage --agent gemini-cliProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/trailofbits/skills.git skills-src && mkdir -p .gemini/skills && cp -r skills-src/plugins/github-triage/skills/github-triage .gemini/skills/github-triage && rm -rf skills-srcUse ~/.gemini/skills/ instead of .gemini/skills for a personal install, then run /skills reload.
Gemini CLI skills documentation · loads skills from .gemini/skills/, .agents/skills/
Install the "github-triage" agent skill from https://github.com/trailofbits/skills/tree/main/plugins/github-triage/skills/github-triage into .gemini/skills/github-triage/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "github-triage", then confirm the skill loads.Gemini CLI copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ gh skill install trailofbits/skills github-triageInstalls for Copilot at project scope by default; add --scope user for a personal install. Preview a skill first with gh skill preview. Needs GitHub CLI 2.90.0 or later (public preview).
$ npx skills add trailofbits/skills --skill github-triage -a github-copilotProject install goes to .agents/skills/; add -g for ~/.copilot/skills/.
$ git clone --depth 1 https://github.com/trailofbits/skills.git skills-src && mkdir -p .github/skills && cp -r skills-src/plugins/github-triage/skills/github-triage .github/skills/github-triage && rm -rf skills-srcUse ~/.copilot/skills/ instead of .github/skills for a personal install. Commit .github/skills so cloud agent and code review can use it.
GitHub Copilot skills documentation · loads skills from .github/skills/, .claude/skills/, .agents/skills/
Install the "github-triage" agent skill from https://github.com/trailofbits/skills/tree/main/plugins/github-triage/skills/github-triage into .github/skills/github-triage/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "github-triage", then confirm the skill loads.GitHub Copilot copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
$ npx skills add trailofbits/skills --skill github-triage -a opencodeOpenCode documents no install command of its own. Project install goes to .agents/skills/; add -g for ~/.config/opencode/skills/.
$ gh skill install trailofbits/skills github-triage --agent opencodeProject scope by default (.agents/skills/); add --scope user for a personal install.
$ git clone --depth 1 https://github.com/trailofbits/skills.git skills-src && mkdir -p .opencode/skills && cp -r skills-src/plugins/github-triage/skills/github-triage .opencode/skills/github-triage && rm -rf skills-srcUse ~/.config/opencode/skills/ instead of .opencode/skills for a personal install.
OpenCode skills documentation · loads skills from .opencode/skills/, .claude/skills/, .agents/skills/
Install the "github-triage" agent skill from https://github.com/trailofbits/skills/tree/main/plugins/github-triage/skills/github-triage into .opencode/skills/github-triage/ in this project. Copy the whole folder (SKILL.md and every file beside it), keep the folder name "github-triage", then confirm the skill loads.OpenCode copies the folder itself, the same result as the manual copy. Check what it changed before you commit it.
github-triageTriages open GitHub issues and pull requests with the gh CLI, optionally merging ready PRs, closing resolved issues with evidence and assigning local priority and size estimates.
Started with /github-triage, the skill first picks the target repository by checking gh authentication and the GitHub-hosted remotes. It can optionally clear ready PRs first, merging passing bot PRs and maintainer-approved ones and spawning review subagents for PRs nobody has reviewed. It then closes issues already resolved by a cited PR or commit, cross-links issues with the PRs that fix them, and gives each outstanding issue a priority and change-size estimate.
Writes are gated: the whole triage is computed and every proposed merge, closure and comment is presented before anything executes, and you must approve. Closing needs concrete evidence, with ambiguous cases left open and flagged. Priority and size stay local and never become GitHub labels. The skill runs only on explicit invocation, and a reference file covers how to review PRs.
7 steps, taken from the step headings in SKILL.md.
Read from SKILL.md and the folder at commit 82fe822. It shows what the files ask for, not the result of running them.
Pre-approves these tools, so the agent can use them without asking each time:
BashReadGrepAgentAskUserQuestionWriteFrom allowed-tools in the SKILL.md frontmatter.
Shell commands in SKILL.md call:
ghgitFrom the folder's file list and the shell code blocks in SKILL.md.
Hosts in commands or code, which the agent is likely to contact:
github.comFrom URLs in SKILL.md, links to its own repository left out.
Names no API keys, tokens, secrets or passwords.
From names ending in _API_KEY, _TOKEN, _SECRET, _KEY or _PASSWORD in SKILL.md.
GitHub Triage loads about 5.8k tokens when it runs, and up to ~6.6k if it reads all its reference files. Until then it costs about 140 tokens; SKILL.md has 2,729 words of instructions outside code blocks.
Estimates: characters ÷ 4, the usual rule of thumb; real counts depend on the model's tokenizer. Scripts and assets cost tokens only if the agent reads them.
The automated check noted patterns worth knowing about, such as sudo or a known installer.
allowed-tools: Bash, Read, Grep, Agent, AskUserQuestion, WriteAutomated static check — not a guarantee. Review scripts before installing. It scans the text of SKILL.md for risky patterns (piping downloads into a shell, reading credential files, hidden Unicode, destructive commands); files beside SKILL.md are not scanned.
The full file from trailofbits/skills at commit 82fe822, republished under its CC-BY-SA-4.0 licence (© trailofbits). 2,729 words, ~5,763 tokens.
.claude/skills/github-triage/SKILL.md (or your agent's skills folder). This skill also uses 3 other files; get the full folder from GitHub.Triage a repository's open GitHub issues and pull requests. Optionally clear ready PRs first (merge passing bot PRs and maintainer-approved PRs, review never-reviewed ones), then close issues that are already resolved (with a comment citing the PR or commit that resolved them), make sure issues and the pending PRs that fix them reference each other, and assign a local-only priority and change-size estimate to every issue that is still outstanding.
/github-triage to groom or review a repository's open issues
and pull requests.gh auth status # confirm gh is authenticated
git rev-parse --is-inside-work-tree # is PWD a git repository?
git remote -v # enumerate remotesDetermine the set of distinct GitHub-hosted repositories among the remotes.
A remote is GitHub-hosted when its URL host is github.com, in any of these forms:
https://github.com/OWNER/REPO(.git)git@github.com:OWNER/REPO(.git)ssh://git@github.com/OWNER/REPO(.git)Normalize each to OWNER/REPO and de-duplicate (a fork setup may have origin
and upstream pointing at different GitHub repos; multiple remotes pointing at
the same OWNER/REPO count once).
Selection rule:
| Situation | Action |
|---|---|
| Exactly one distinct GitHub repo | Use it as the default — do not prompt. |
| Not a git repo, or zero GitHub remotes | Ask the user for the OWNER/REPO to triage. |
| More than one distinct GitHub repo | Use AskUserQuestion to let the user pick which OWNER/REPO. |
GitHub Enterprise hosts cannot be auto-detected reliably. If the user works on a GHE instance, ask for
OWNER/REPOand have them setGH_HOST/ usegh's configured host. Confirm the resolved repo back to the user before continuing.
Store the result as REPO="OWNER/REPO" and pass -R "$REPO" to every gh call.
Validate it against ^[A-Za-z0-9._-]+/[A-Za-z0-9._-]+$ before use, so a malformed or
hostile remote URL never flows into a command.
# Open issues (gh issue list excludes PRs by default)
gh issue list -R "$REPO" --state open --limit 1000 \
--json number,title,body,labels,assignees,comments,reactionGroups,createdAt,updatedAt,url
# Open PRs — candidates for "pending fix" (issue phase) and PR triage (Phase 2)
gh pr list -R "$REPO" --state open --limit 1000 \
--json number,title,body,author,isDraft,reviewDecision,latestReviews,\
mergeable,mergeStateStatus,statusCheckRollup,labels,createdAt,headRefName,url,closingIssuesReferences
# Recently merged PRs — candidates for "already resolved"
gh pr list -R "$REPO" --state merged --limit 300 \
--json number,title,body,mergedAt,url,closingIssuesReferencesclosingIssuesReferences lists the issues a PR is linked to close — populated by any
of GitHub's closing keywords (close/closes/closed, fix/fixes/fixed,
resolve/resolves/resolved) in the PR body, or by a manual UI link. It is the
strongest available signal. For issues it does not cover, also search commit messages
on the default branch. Resolve the default branch authoritatively from the selected
repo (not from local origin/HEAD, which may be unset or point at the wrong remote):
default_branch=$(gh repo view "$REPO" --json defaultBranchRef --jq .defaultBranchRef.name)
# Anchor the issue number so #12 does not match #120, #123, …
git log --oneline "origin/$default_branch" \
| grep -iE "(close|fix|resolve)[sd]? +#<N>([^0-9]|$)"Handle PRs before issues: merging ready PRs here means the "already resolved" check in the issue phase sees the work those merges just landed.
If there are no open PRs, skip this phase. Otherwise summarize the open PRs and ask the user whether to handle PRs now (AskUserQuestion: handle PRs / skip to issues). If they skip, go straight to issue classification.
Classify each open PR from its review, CI, and merge state. The field shapes below are
what gh pr ... --json actually returns — rely on them, not on intuition:
mergeable == "MERGEABLE" and mergeStateStatus == "CLEAN"
and CI is not blocking (below). CLEAN is GitHub's server-side "no conflicts,
not behind, not draft, required checks green" verdict — any other mergeStateStatus
(BEHIND, UNSTABLE, BLOCKED, DIRTY, DRAFT, …) is not ready. Treat
mergeable == "UNKNOWN" (GitHub recomputes mergeability lazily, especially right
after another merge) as not ready — re-poll briefly or skip; never merge on it.statusCheckRollup (keyed on __typename) and reject the
PR only on a real problem: a hard failure (a CheckRun whose conclusion is
FAILURE/CANCELLED/TIMED_OUT/ACTION_REQUIRED/STARTUP_FAILURE/STALE, or a
StatusContext whose state is FAILURE/ERROR), or anything still running (a
CheckRun status of QUEUED/IN_PROGRESS/WAITING/PENDING, or a
StatusContext state of PENDING/EXPECTED — wait, do not merge yet). SUCCESS,
NEUTRAL, and SKIPPED are fine and must not block (e.g. a CodeQL run that
reports NEUTRAL on a dependency bump — a green Dependabot PR is CLEAN with a
NEUTRAL CodeQL check). An empty rollup is no CI — a distinct state, never
treated as ready. mergeStateStatus == "CLEAN" already reflects required-check
status; use the rollup to catch failing/pending non-required checks.author.is_bot == true. Match the auto-merge allowlist
against author.login after normalizing away gh's rendering — strip a leading
app/ and a trailing [bot] (gh returns Dependabot as app/dependabot in some
versions and dependabot[bot] in others; normalize both to dependabot).
Allowlisted by default: dependabot, renovate, plus any the user names. A passing
PR from a non-allowlisted bot is reported, never offered for merge.latestReviews has an entry with state == "APPROVED"
whose authorAssociation is OWNER/MEMBER/COLLABORATOR and whose author.login
is not the PR author. Do not use reviewDecision == "APPROVED" alone: it is
branch-protection-driven and is null on repos with no required-review rule, so it
both over-trusts (cannot confirm write access) and misses genuine approvals.latestReviews has no APPROVED/CHANGES_REQUESTED entry from
anyone other than the PR author (a fork "review disabled" bot comment is not review).| Category | Condition | Offered action |
|---|---|---|
| Mergeable bot PR | allowlisted bot + ready + not draft | Offer incremental, in-order merge |
| Approved & ready | maintainer-approved + ready + not draft | Prompt to merge |
| Never reviewed | non-bot + only the author has reviewed (or no reviews) + not draft | Offer to spawn a review subagent |
| Needs work | draft, hard CI failure, pending CI, conflicts, behind, changes requested, or a bot PR that is not ready | Report only — no action offered |
("ready" already subsumes CI-not-blocking. Review subagents are for human-authored PRs — a bot's dependency bump that is not ready is reported as Needs work, not reviewed.)
Present the categorized PRs and offer the applicable actions via AskUserQuestion.
Incremental, in-order merge (bot PRs and approved-ready PRs): confirm the merge set and the merge method first (merging is irreversible), then merge one at a time, oldest first. Choose a method the repo allows and fail closed if it does not:
gh repo view "$REPO" --json mergeCommitAllowed,squashMergeAllowed,rebaseMergeAllowed # positional repo, not -RFor each PR in order, re-verify immediately before merging (state drifts after each
merge — a landed PR can leave the next BEHIND, conflicting, or recomputing), merge
synchronously, then confirm it landed before advancing:
gh pr view <N> -R "$REPO" --json isDraft,reviewDecision,mergeable,mergeStateStatus,statusCheckRollup
# proceed only if still ready: not draft, mergeable == MERGEABLE, mergeStateStatus == CLEAN, CI not blocking
gh pr merge <N> -R "$REPO" --<method> # never --auto, never --admin
gh pr view <N> -R "$REPO" --json state # expect "MERGED" before moving to the next PRStop and report if a PR is no longer ready (including mergeable == "UNKNOWN"), if the
merge did not land, or if any required check is not green — never force, --admin,
--auto, or skip a check. For bot PRs, surface the dependency and version jump (e.g.
major bumps) in the gate so the user can decide with context.
Review subagents (never-reviewed PRs): when the user opts in, spawn one Agent
per PR — all in a single assistant message so they run in parallel. Each subagent
reviews one PR's diff and returns a structured review; write each to
github-pr-<number>-review.md in the working directory (overwriting any prior file
for that PR). Reviews are read-only and never posted to GitHub. See
references/reviewing-prs.md for the subagent prompt,
review rubric, and file format.
After any merges, re-fetch the merged-PR list (the Phase 1 --state merged query)
so the issue phase can detect issues those merges resolved.
Sort every open issue into exactly one bucket.
Bucket A — Already resolved (the work landed, the issue was left open). Requires concrete evidence; prefer corroboration over a single weak signal:
closingIssuesReferences, or its title/body
references #N alongside a closing keyword. (Strongest.)#N with a closing keyword.→ Proposed write: close the issue with a comment that names the resolving PR/commit.
gh issue close <N> -R "$REPO" \
-c "Resolved by #<PR> (<short reason>). Closing as the change is now on $default_branch."Bucket B — Pending PR would resolve it (an open PR addresses the issue).
Detect by either direction: the open PR's closingIssuesReferences includes the
issue; the issue body/comments link the PR; or an open PR clearly fixes the same
thing the issue describes. The goal is that the issue and PR reference each
other — fill only genuine gaps, and prefer non-destructive writes:
#<PR> / #<N> creates a cross-reference that
GitHub mirrors into the other's timeline.gh issue comment <N> -R "$REPO" -b "A fix is in progress in #<PR>."Do not edit the PR body unless the user explicitly wants the PR to auto-close the issue on merge. A comment establishes a reference but does not trigger auto-close — only a closing keyword in the PR body or a commit message does. When the user opts in, never clobber the description: re-fetch the body immediately before editing and pass it via stdin, so untrusted PR text never transits a shell-interpolated string:
body=$(gh pr view <PR> -R "$REPO" --json body --jq .body)
printf '%s\n\nCloses #%s\n' "$body" "<N>" | gh pr edit <PR> -R "$REPO" --body-file -Do not duplicate links that already exist.
Bucket C — Outstanding (no resolution, no pending PR). Assign, locally only, a priority and a change-size estimate (next section).
Priority — Critical / High / Medium / Low. Weigh:
security, bug,
crash, regression) are strong signals.Change size — the effort proxy, expressed as a size/* T-shirt bucket from the
estimated total changed lines (additions + deletions, ignoring generated/vendored
files), using the Kubernetes/Prow thresholds:
| Bucket | Estimated changed lines |
|---|---|
size/XS | 0–9 |
size/S | 10–29 |
size/M | 30–99 |
size/L | 100–499 |
size/XL | 500–999 |
size/XXL | 1000+ |
Estimate by reasoning about the codebase: which files/areas the change touches and whether it is localized or cross-cutting. When feasible, open the implicated files (Grep/Read) before estimating rather than guessing from the title — the line and file counts should reflect what the change actually touches. Because the issue is not yet implemented, the diff is a prediction — so:
unsized — needs investigation instead of guessing.Never post priority, size, or the basis of estimate to GitHub.
Present everything in one view. Make the local-only section unmistakably local.
## Triage for OWNER/REPO (N open issues)
### Proposed closes (already resolved) — WRITES to GitHub
| Issue | Title | Evidence | Draft comment |
|-------|-------|----------|---------------|
| #123 | ... | merged PR #130 | "Resolved by #130 …" |
### Proposed cross-links (pending PR) — WRITES to GitHub
| Issue | PR | Gap | Proposed action |
|-------|----|-----|-----------------|
| #140 | #145 | PR omits `Closes #140` | edit PR body to add closing ref |
| #141 | #146 | none | already linked — no action |
### Outstanding issues — LOCAL ONLY, never posted to GitHub
| Issue | Title | Priority | Size | Est. lines / files | Basis |
|-------|-------|----------|------|--------------------|-------|
| #150 | ... | High | size/M | ~60 / 3 | "validation + 2 call sites + test" |
**Summary:** X to close, Y to cross-link, Z outstanding.Then ask for approval with AskUserQuestion. Per the user's chosen gating (batch review with iteration), offer options such as:
If the user chooses Revise first, iterate conversationally: let them drop specific closes, downgrade weak evidence to "leave open / needs review", edit any draft comment, or adjust a cross-link. Re-present the revised write set and ask again. Loop until the user approves the final set. Execute nothing until then.
Run each approved write as a separate command so one failure does not block the rest. Report the outcome of each, and continue past failures.
gh issue close 123 -R "$REPO" -c "Resolved by #130 …"
gh issue comment 140 -R "$REPO" -b "A fix is in progress in #145."
# Only if the user opted into auto-close-on-merge for #145 (re-fetch body, pass via stdin):
body=$(gh pr view 145 -R "$REPO" --json body --jq .body)
printf '%s\n\nCloses #140\n' "$body" | gh pr edit 145 -R "$REPO" --body-file -Let K be the number of outstanding (Bucket C) issues.
K ≤ 32 → render the outstanding table directly in the response.
K > 32 → offer to save the results to disk instead of printing a large table. Use AskUserQuestion (save to file / print anyway). When saving, write a Markdown file with the Write tool:
github-triage-OWNER-REPO-YYYYMMDD.md(derive the date from date +%Y%m%d; write into the current directory unless the
user specifies a path). The file contains the summary plus the full outstanding
table (Issue, Title, Priority, Size, Est. lines/files, Basis), sorted by priority
then size. Confirm the saved path back to the user.
The threshold applies to the outstanding table — the thing being displayed. 32 is the default; honor a different cutoff if the user requests one.
disable-model-invocation).gh pr merge, gh issue close, gh issue comment, or gh pr edit.--body-file - (stdin) or -F,
never inline in --body "…", so backticks or $(…) in third-party text cannot
execute.github-pr-<number>-review.md and never posted to GitHub. The
subagent recommendation is advisory — it never triggers a merge on its own.| Rationalization | Why it's wrong |
|---|---|
| "The issue is old, it's probably resolved." | Age says nothing about resolution. Old issues are often still valid. |
| "A PR mentions this issue, so close it." | Only a merged PR that actually addresses the issue resolves it. An open or closed-unmerged PR does not. |
| "The feature looks implemented — close it." | Verify in the current code. A partial or differently-scoped implementation does not satisfy the issue. |
| "Closing auto-generates a note, so no comment is needed." | Always leave a human-readable reason citing the resolving PR/commit. |
| "Posting the priority as a label would be helpful." | Priority and size are local-only by explicit instruction. Never post them. |
| "They obviously reference each other already, skip checking." | Verify the bidirectional reference actually exists before claiming it; only skip the write when a link is genuinely present. |
| "There are too many issues, I'll just sample the first 32." | The 32 threshold governs display, not coverage. Triage every open issue; save to disk when the table is large. |
| "This issue is hard to size, I'll guess size/M." | Mark it unsized — needs investigation. A fabricated estimate is worse than an honest unknown. |
| "All the bot PRs are green, so merge them all at once." | Merge in order, one at a time. Each merge can stale or conflict the next; re-check before every merge. |
| "The author approved their own PR, so it's been reviewed." | An author approving their own PR is not review. "Maintainer-approved" requires an approval from someone else with write access. |
| "CI passed, so the PR is safe to merge." | CI passing is necessary, not sufficient. A never-reviewed PR still gets a review (or stays unmerged); surface major dependency bumps before merging bot PRs. |
| "The subagent recommended approve, so merge it." | The review is advisory and local. Merging is a separate, explicitly-approved action — never chained off a review verdict. |
| "An unrecognized bot opened a green PR, so merge it." | Only merge bots on a trusted allowlist (Dependabot/Renovate/user-named). An unknown App could be hostile or misconfigured. |
"mergeable is true, so it's safe to merge." | Require mergeStateStatus == CLEAN. MERGEABLE can still be BEHIND/BLOCKED/UNSTABLE, and UNKNOWN means GitHub has not recomputed yet — never merge on it. |
© trailofbits, CC-BY-SA-4.0. Rendered from Markdown: HTML in the file is shown as text, images as links, and headings moved down two levels. Raw file
SKILL.md and 3 other files (references, assets) in plugins/github-triage/skills/github-triage of trailofbits/skills.
Open the folder on GitHubat commit 82fe822
GitHub Triage next to the 5 skills that share the most tags, products or categories with it. Stars are the repository's; “used in” counts other GitHub owners with a copy.
| Skill | Stars | Used in | Tokens | Auto-check | Licence | Repo updated |
|---|---|---|---|---|---|---|
| GitHub Triage this skilltrailofbits/skills | 7.4k | — | ~5.8k | Automated safety check: Notes | CC-BY-SA-4.0 | |
| Pre-Release PR Triagejamiepine/voicebox | 57k | — | ~3.1k | Automated safety check: Pass | MIT | |
| Contributor-First PR MergeHKUDS/OpenHarness | 16k | 1 repos | ~847 | Automated safety check: Pass | MIT | |
| Ansible Backport Creatoransible/ansible | 71k | — | ~1.2k | Automated safety check: Pass | GPL-3.0 | |
| Ouroboros Maintainer TriageQ00/ouroboros | 6.2k | — | ~1.7k | Automated safety check: Pass | MIT | |
| RTK Issue Triagertk-ai/rtk | 83k | — | ~3k | Automated safety check: Notes | Apache-2.0 |
jamiepine/voicebox
Sorts a backlog of open pull requests into must-merge, candidate, superseded and deferred, writes a triage doc and works the merge loop before a release.
HKUDS/OpenHarness
Merges external GitHub pull requests while keeping the original author credited, and fixes conflicts after the merge instead of rewriting the contribution.
ansible/ansible
Creates backports of a merged Ansible devel pull request onto the right stable branches by cherry-picking its merge commit onto new backport branches.
Q00/ouroboros
Triages and works through GitHub issues and pull requests in the Q00/ouroboros repo as a maintainer, within a stated review boundary and clear limits on what it may change.
rtk-ai/rtk
Audits open GitHub issues, categorizes them, flags duplicates and linked PRs in three phases, with optional deep analysis and comments posted only after validation.
rtk-ai/rtk
Audits a repository's open pull requests, deep-reviews chosen ones and drafts review comments that are only posted after you approve them.
trailofbits/skills
Scans a codebase for vulnerabilities with CodeQL's data flow and taint tracking in run-all or important-only modes, including data extensions for project-specific sources and sinks.
trailofbits/skills
Generates Mermaid diagrams from Trailmark code graphs, including call graphs, class hierarchies, module dependency maps, complexity heatmaps and attack surface data flows.
trailofbits/skills
Compares Trailmark code graphs at two snapshots, such as commits, tags or directories, to surface attack paths, blast radius and taint changes that text diffs miss.
trailofbits/skills
Draws a 12 Houses tarot spread to break ties when a request is vague or casually delegated, then reads the cards to pick the next step.
trailofbits/skills
Detects languages, proposes rulesets for approval, then runs the approved Semgrep scan across a codebase and merges the output into one SARIF file.
trailofbits/skills
Searches and extracts data from Burp Suite project files on the command line: regex searches over responses, audit findings, proxy history and site map data.
Categories
Triages open GitHub issues and pull requests with the gh CLI, optionally merging ready PRs, closing resolved issues with evidence and assigning local priority and size estimates. Started with /github-triage, the skill first picks the target repository by checking gh authentication and the GitHub-hosted remotes. It can optionally clear ready PRs first, merging passing bot PRs and maintainer-approved ones and spawning review subagents for PRs nobody has reviewed.
GitHub Triage fits situations like: grooming a repository's backlog of open issues; closing issues that merged work already resolved; clearing passing dependency bumps and approved pull requests; linking issues to the pending PRs that fix them.
Run `npx skills add trailofbits/skills --skill github-triage -a claude-code`. Or copy the skill folder (plugins/github-triage/skills/github-triage in trailofbits/skills) into .claude/skills/github-triage in your project. Claude Code loads it when a task matches its description.
Run `npx skills add trailofbits/skills --skill github-triage -a codex`. Or copy the skill folder (plugins/github-triage/skills/github-triage in trailofbits/skills) into .agents/skills/github-triage in your project. Codex loads it when a task matches its description.
Cursor, Gemini CLI, GitHub Copilot and OpenCode also load SKILL.md folders. With the skills CLI, run `npx skills add trailofbits/skills --skill github-triage -a cursor` (or -a gemini-cli, github-copilot or opencode for the others). To copy it by hand, put the folder in .cursor/skills/github-triage, .gemini/skills/github-triage, .github/skills/github-triage and .opencode/skills/github-triage in your project.
Going by SKILL.md and its folder, GitHub Triage needs the command-line tools its instructions call (gh and git). Our summary lists: The GitHub CLI (gh), authenticated; A Git repository with a GitHub remote. Its frontmatter pre-approves these tools: Bash, Read, Grep, Agent, AskUserQuestion, Write.
SKILL.md names 1 domain. In commands or code: github.com; the agent is likely to contact it when it follows the instructions. This is read from the text; nothing was executed.
Our automated static check of SKILL.md found notes only (pre-approves every shell command (allowed-tools: bash)), nothing it rates as a warning. It is not a guarantee. Review the folder before installing.
GitHub Triage is published under the CC-BY-SA-4.0 licence (the repository's licence). It allows redistribution, so the full SKILL.md is shown on this page.
About 5.8k tokens (SKILL.md is roughly 23k characters). Agents keep only the skill's name and description in context until a task matches; then they load SKILL.md in full. Its references folder adds about 788 tokens, read only when the agent opens those files.
Skills that share tags, products or a category with GitHub Triage: Pre-Release PR Triage (jamiepine/voicebox, 57k stars), Contributor-First PR Merge (HKUDS/OpenHarness, 16k stars), Ansible Backport Creator (ansible/ansible, 71k stars) and Ouroboros Maintainer Triage (Q00/ouroboros, 6.2k stars). The comparison table on this page puts their stars, adoption, token cost, safety result and licence side by side.
trailofbits (a GitHub organization, an official publisher) maintains it in trailofbits/skills, which has 7,400 GitHub stars. The repository holds 79 skills in this directory. The repository was last updated on October 2, 2026.
Source: trailofbits/skills on GitHub. Facts on this page come from the repository at the commit we read; the author's words are quoted as theirs.