pr-status — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited pr-status (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Show enriched status for all open PRs created by you across your GitHub org: CI checks, approvals, and unresolved review threads. Also shows recently closed PRs.
The GitHub org is auto-detected from the current repo's origin remote, or can be overridden via PR_STATUS_ORG env var.
/pr-statusMUST re-fetch on every invocation. Each/pr-statustick (including silent ones inside/watch-prs) MUST run all three fetch scripts (gh-pr-list-open.sh,gh-pr-list-closed.sh,gh-pr-details.sh) ANDdate +%H:%M:%S— even if the previous tick was seconds ago. NEVER reuse prior tool output and NEVER extrapolate the timestamp by adding the loop interval to the previous one. If the data looks identical, render "No changes" — but only after a real fetch confirms it. State changes (merges, approvals, CI flips) happen between ticks; reusing stale tables has caused real merges to be missed.
~/.claude/skills/pr-status/scripts/gh-pr-list-open.shOutput is one JSON object per line: {number, title, owner, repo}.
The script searches across the GitHub org — not just the current repo. Org is resolved in order: PR_STATUS_ORG env var, or extracted from the current repo's origin remote URL.
Default lookback is 3 days, extended to 4 on Tuesdays and kept at 3 on Mondays so the previous Friday's PRs stay visible across the weekend. Pass an explicit number of days as the second arg to override.
~/.claude/skills/pr-status/scripts/gh-pr-list-closed.shOutput is one JSON object per line: {number, title, owner, repo, closedAt}.
Group the PRs by owner/repo. For each group, fetch all data in a single GraphQL call:
~/.claude/skills/pr-status/scripts/gh-pr-details.sh {owner} {repo} {number1} {number2} ...Output is a JSON array, one object per PR:
[
{
"number": 123,
"base": "main",
"isDraft": false,
"mergeState": "CLEAN",
"reviewDecision": "APPROVED",
"approvers": ["alice"],
"unresolvedThreads": 2,
"checksState": "SUCCESS",
"lastPush": "2026-04-15T09:30:00Z",
"mergeCommitSha": "abc123...",
"mergeCommitAt": "2026-04-15T10:00:00Z",
"mainChecksState": "SUCCESS",
"readyAt": "2026-04-14T15:32:11Z"
}
]Map checksState values: SUCCESS → ✅ / FAILURE or ERROR → ❌ / PENDING or EXPECTED → ⏳ / null → —
#### Fallback (if batch script unavailable): fetch per PR in parallel
CI status (pass / failing / pending):
~/.claude/skills/pr-status/scripts/gh-pr-checks.sh {number}If the script is unavailable, fall back to:
gh pr checks {number} 2>/dev/null | awk -F'\t' '{print $2}' | sort | uniq -cApprovals (count + who):
~/.claude/skills/pr-status/scripts/gh-pr-reviews.sh {owner} {repo} {number}If the script is unavailable, fall back to:
gh api "repos/{owner}/{repo}/pulls/{number}/reviews" \
--jq '[.[] | select(.state == "APPROVED") | .user.login] | unique | join(", ")'Unresolved review threads (count):
~/.claude/skills/pr-status/scripts/gh-pr-threads.sh {owner} {repo} {number}If the script is unavailable, fall back to:
gh api graphql -f query='
query($owner:String!,$repo:String!,$pr:Int!){
repository(owner:$owner,name:$repo){
pullRequest(number:$pr){
reviewThreads(first:100){
nodes{ isResolved }
}
}
}
}' -f owner="{owner}" -f repo="{repo}" -F pr={number} \
--jq '[.data.repository.pullRequest.reviewThreads.nodes[] | select(.isResolved==false)] | length'Merge state (behind / conflict / clean):
~/.claude/skills/pr-status/scripts/gh-pr-merge-state.sh {number} {owner} {repo}If the script is unavailable, fall back to:
gh pr view {number} --repo {owner}/{repo} --json mergeStateStatus --jq '.mergeStateStatus'Before the tables, output a timestamp line: _Checked at HH:MM:SS_ in local time, 24h. Run date '+%H:%M:%S' (do NOT use date -u — that's UTC). Note: relative-time math against lastPush / readyAt / closedAt still works in UTC since those fields are Z-suffixed; only the displayed timestamp needs to be local.
Recently closed — render first. Show in a single table with a Repo column. Skip this section entirely if the closed list is empty. Fetch details for closed PRs too (same gh-pr-details.sh script) to get readyAt.
#### Recently closed
| PR | Repo | Ticket | Title | Status | CI | Ready | Wait | Closed |
|---|
[#123](https://github.com/{owner}/{repo}/pull/123)/[A-Z]+-\d+/ against the PR title. Show as plain text or —merged field in closed list output)mainChecksState). Only check if merged and mergeCommitAt is within the last 2 days — otherwise show —. Map: SUCCESS → ✅ / FAILURE or ERROR → ❌ / PENDING or EXPECTED → ⏳ / null or unmerged → —readyAt). Same short unitsclosedAt - readyAt). Shows how long the PR waited for review/merge2h, 1d, 5dOpen PRs — render after closed. Group by repo. For each repo that has open PRs, output a heading #### Open — {repo} followed by a table. Only show repos that have PRs — don't list empty repos.
| PR | Ticket | Title | Branch | Target | Ready | Push | Sync | CI | Approved | Threads | LGTM |
|---|
[#123](https://github.com/{owner}/{repo}/pull/123)AB-1107) by matching /[A-Z]+-\d+/ against the branch name first, then the PR title. Show as plain text. If no match, show —feat/, fix/, etc.) and the Jira ticket prefix (already shown in the Ticket column), e.g. feat/AB-1107-cta-clicked-event → cta-clicked-event. If still over ~30 chars after stripping, truncate with ….main/master, indicating the PR is stacked on another branch and should not be merged directly.main → main (no 📌)master → master (no 📌)feat/parent-pr → 📌 feat/parent-prmain or master (both are default branches); for stacked PRs (base is something else, i.e. Target has 📌) show — since the PR can't merge directly anyway.main or master → use mergeState below—CLEAN or UNSTABLE → ✅BEHIND → ⚠️ behindDIRTY → 💥 conflict—isDraft is true (PR is in draft, not yet ready for review). Otherwise relative time since PR became ready for review (from readyAt — uses ReadyForReviewEvent or PR createdAt as fallback). Same short unitslastPush in details output), e.g. 2h, 1d, 3d. Use short units: Nm for minutes, Nh for hours, Nd for daysreviewDecision is APPROVED (e.g. two approvers → ✅✅). If reviewDecision is REVIEW_REQUIRED but approvers exist, the approvals are stale (invalidated by a newer push) — render one ☑️ per stale approver. If reviewDecision is CHANGES_REQUESTED, show 👎 (ignore stale approvals). If there are no approvers and no changes requested, show 🔔 to flag that the PR is awaiting review and needs pinging — unless isDraft is true, in which case show — (no point chasing a draft).💬 N if N > 0, or — if zeroisDraft is false, reviewDecision is APPROVED, CI is SUCCESS, sync is CLEAN or UNSTABLE, threads is 0, and mergeState is CLEAN. Otherwise 🚧 (still under construction — something's blocking the merge).Keep PR titles truncated:
After the tables, if anything changed since the last check in this session, list the deltas as a bullet list, e.g.:
If nothing changed, say "No changes."
Always render both the Open and Recently closed tables in full, even on unchanged ticks. The point of repeated checks (via /watch-prs or otherwise) is to see current state at a glance — collapsing to "No changes." or omitting the closed section forces scrolling back to find prior state, which defeats the glance. With the narrow closed-PR window (3–4 days) there's not much to render anyway.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.