PR list by author (awaiting merge / not fully approved)
Overview
Produces a deduped list of open pull requests authored by a chosen set of GitHub users, excluding drafts and PRs whose reviewDecision is APPROVED. Supports per-repo queries (exact review state, low cost) or cross-repo search (broad coverage, optional per-PR follow-up). Works on GitHub Enterprise or github.com when gh is authenticated.
Default: Mode A (enumerated REPOS) after collecting AUTHORS and repo slugs. Slack: if the user sets share=channel / share=canvas, passes canvas=https://…enterprise.slack.com/docs/T…/F…, or asks in natural language to post to a channel or canvas.
When to use
- Daily or ad-hoc triage of open PRs from a team-defined set of GitHub usernames.
- Criteria: not draft,
reviewDecisionis notAPPROVED(includesREVIEW_REQUIRED,CHANGES_REQUESTED, and empty string). - Works on GitHub Enterprise or github.com; set
GH_HOSTto the enterprise hostname when not using github.com (omit orunset GH_HOSTfor public GitHub ifghdefault applies).
Usage
Interactive (nothing supplied yet)
/pr-list-by-author
Or natural language, e.g. “What PRs are waiting on review for our team?”
- Follow First step: obtain
AUTHORSbelow (prompt if missing). - Ask for
REPOSas comma- or space-separatedowner/reposlugs unless the user asks for “all repos” / cross-repo → then use Mode B or C.
Authors and repos in one message
/pr-list-by-author alice bob carol repos=acme/platform,acme/lib-ui
Natural language examples:
- “Open PRs from
aliceandbobonacme/platformandacme/lib-ui.” authors="alice bob" repos="org/repo-a, org/repo-b"(normalize quotes and spacing).
Normalize repos= the same way: split on commas, trim whitespace, strip a leading / from each slug (e.g. /mc-content-platform/loom → mc-content-platform/loom).
GitHub team instead of a manual author list
User says e.g. team: my-org/my-team or “use GitHub team my-org/content-platform”:
- Resolve logins:
gh api orgs/ORG/teams/TEAM_SLUG/members --paginate(withGH_HOSTif Enterprise). - Use the resulting list as
AUTHORS; continue with Mode A or B as requested.
Cross-repo or org-scoped search
User asks for PRs across all repos they can access:
- Mode B:
gh search prsper author → merge →gh pr viewper candidate for accuratereviewDecision(B1), unless they explicitly accept approximate--reviewfilters (B2). - Mode C: Same as B but add
orgs=org1,org2(maps to repeated--ownerongh search prs).
Example prompts:
- “Search all my repos for open PRs from these authors that aren’t fully approved.”
- “Limit search to
mailchimp-monolithandmc-content-platformorgs.”
Share to Slack
/pr-list-by-author alice bob repos=org/a share=channel slack=#my-team
- “Post the results to
#my-team-channel.” - “Put this list in a Slack canvas for the team.”
canvas=https://…enterprise.slack.com/docs/T…/F…— treat likeshare=canvaswith a standing canvas URL; after the in-chat table, build a markdown block for this run and append it via Slack MCP (or paste) per Optional — Post to Slack.
If share=channel is set but slack is missing for the channel, prompt once for #channel or ID.
Optional knobs (conversation or key=value)
| Knob | Purpose |
|---|---|
limit=N |
Per-author gh pr list cap (Mode A) or -L for search (Mode B/C); max 1000 for search. |
GH_HOST=host |
Enterprise hostname (e.g. github.your-company.com); omit for default github.com behavior. |
share=channel |
After results, post to a Slack channel (see Optional — Post to Slack). |
share=canvas |
After results, update or create a Slack canvas (see below). |
canvas=https://…/docs/T…/F… |
Enterprise Slack canvas / docs URL — same intent as share=canvas; canvas_id = the F… segment for Slack MCP tools. |
slack=#channel or slack=C0123… |
Destination channel when share=channel (name with # or channel ID). |
Output
Present a table: repo, PR #, author, reviewDecision, title, clickable URL. Mention dedupe (by URL) and repos not scanned if using Mode A only.
If the user asked to share to Slack (share=channel, share=canvas, canvas= URL, or natural language such as “post to #team”, “put this in a canvas”), run Optional — Post to Slack after the table.
First step: obtain AUTHORS
Do not assume a fixed roster. Before running queries:
- If the user already gave GitHub logins (message, paste, or
@mentions), normalize to a space- or comma-separated list and trim@. - If not provided, prompt once: e.g. “Paste GitHub usernames to include (space- or comma-separated), or say
team: org/team-slugif you want members resolved via the GitHub API.” - Optional: if the user gives
org/team-slug, resolve members withgh api orgs/ORG/teams/TEAM_SLUG/members --paginate -q '.[].login'
(adjust host withGH_HOSTfor Enterprise), then use that list asAUTHORS.
Proceed only after AUTHORS is non-empty.
Enumerated repos vs cross-repo search
| Approach | Best when | Pros | Cons |
|---|---|---|---|
A. Enumerate REPOS |
You know which repos the team ships in. | Exact reviewDecision in one gh pr list call per author per repo; predictable; cheap. |
Misses PRs in repos you never list. |
B. Cross-repo gh search prs |
Authors open PRs across many orgs/repos and the list changes often. | Finds all open PRs by that author across repos your token can search (subject to indexing and permissions). | Search JSON has no reviewDecision → gh pr view per PR for accuracy, or approximate --review flags. More API calls; search cap (~1000). |
| C. Org-scoped search | PRs stay inside a few orgs but many repos. | Narrower than global search. | Same reviewDecision gap as B unless you post-filter with gh pr view. |
Default recommendation: use A with an explicit REPOS list the team owns. Add B when the user wants “any repo” or PRs keep appearing outside that list.
Prerequisites
ghCLI authenticated for the target host (gh auth login; use--hostnamefor Enterprise).jqinstalled.- Slack posting (optional): Cursor Slack MCP (
plugin-slack-slack) when enabled, or another allowed integration; otherwise copy-paste instructions. - GitHub Enterprise: if repos live on github.your-company.com (or another enterprise host) but
ghdefaults to github.com, setGH_HOST=github.your-company.com(or the right hostname) for Mode A/gh pr listand team resolution.
Parameters
| Parameter | Default | Description |
|---|---|---|
AUTHORS |
(prompt) | Required. Space- or comma-separated GitHub logins (@ stripped). Or resolve from team: org/team-slug. |
REPOS |
(prompt for Mode A) | Mode A: one or more owner/repo slugs; loop repos × authors. Omit only if using Mode B (global search). |
LIMIT |
100 |
Mode A: per-author gh pr list --limit. Mode B/C: gh search prs -L (max 1000). |
ORGS |
(none) | Mode C: org names for repeated --owner on gh search prs. |
GH_HOST |
(unset / gh default) |
Enterprise hostname when not using github.com. |
| Mode | A (list) |
A = enumerated repos (gh pr list). B = cross-repo search. C = org-scoped search. |
share |
none |
none = chat only. channel = post to Slack channel. canvas = post to Slack canvas (see below). |
canvas |
(none) | Optional Slack docs URL (…/docs/T…/F…); same workflow as share=canvas (append a run section via MCP or paste). |
slack |
(prompt if share=channel) |
Channel #name or ID C… when posting to a channel. |
Do not apply (unless the user asks)
- No filter on “at least one submitted review”—only
reviewDecision != "APPROVED". - Do not guess which Slack channel or canvas to use—confirm
slack=/ destination (or accept explicit user instruction). - Do not use Slack membership to infer author lists unless the user asked for that mapping.
Optional — Post to Slack (channel or canvas)
Run only when the user requests Slack sharing (see Usage knobs). Complete the PR table first, then post a summary + links.
1. Build the message body
- Title line: e.g.
*Open PRs (awaiting approval)* — {date} UTCor similar. - Bullets or short table in Slack mrkdwn: use
<https://…|owner/repo#123>for each PR; include author andreviewDecisionif space allows. - Footer: note repos scanned (Mode A) or “cross-repo search” (Mode B/
C) and count of PRs (0 is valid).
Keep under Slack message limits; for very long lists, post top N plus “+M more in thread” or split across thread replies.
2. Post to a channel (share=channel)
- Resolve destination: user-supplied
#channelor channel ID. - Use the Slack integration available in the environment (e.g. Slack MCP
plugin-slack-slack, approved internal bot, or documented webhook). Cursor Slack uses normal sign-in—there is nomcp_authon that server. - Send the formatted body. Confirm success with the user (permalink or “posted to #…).
If no Slack tool is available: paste the formatted mrkdwn into chat and tell the user to copy into Slack, or offer a snippet file at /tmp/open-prs-slack-{date}.txt.
3. Post to a canvas (share=canvas or canvas= URL)
Default behavior: treat canvas updates as appends. Slack MCP slack_update_canvas with content typically adds a new section to the canvas rather than replacing the entire file—that is allowed and expected for this skill. Call it after each run when canvas= / share=canvas is set.
- Build one run block: start with a clear run header (e.g.
## Open PRs — {date/time} UTC) so successive runs stay readable. Include a short line for authors, repos scanned, filters, and PR count. Then sections (e.g. by author) with checklist lines linking each PR—or a compact list if empty. Save the same body to/tmp/open-prs-canvas-{YYYY-MM-DD}-{HHMM}Z.md(include time so multiple runs the same day do not overwrite) for reference or manual paste. - Slack MCP (
plugin-slack-slack): usecanvas_id=F…from the docs URL (…/docs/T…/F…). Callslack_update_canvaswithcontentset to that markdown string. Do not use amarkdownparameter — that shape can return-32603. If the tool schema later exposes full replace / overwrite, you may use it when the user explicitly asks to replace the whole canvas; otherwise append is fine. - Canvas hygiene: remind the user that the doc grows with each automated append; they can archive or delete older sections in Slack when it gets long. If they want a one-shot full replace instead, they can select all → paste from the latest
/tmp/open-prs-canvas-*.md(replacing everything manually). - If no canvas tool: post the report as a channel message or give paste steps and the
canvas=link. - Never fabricate canvas IDs; use the user-supplied
canvas=URL or prompt once.
4. Safety
- No secrets in the skill (tokens, webhook URLs). Use env vars or Cursor/MCP auth only.
- Do not post to public or unrelated channels without explicit destination from the user.
Mode A — Enumerated repos (exact reviewDecision)
Export host if needed, e.g.
export GH_HOST=github.enterprise.example.com(or rely onghdefault for github.com).For each
REPOinREPOS, for each login inAUTHORS:gh pr list --repo "$REPO" --author "$login" --state open --limit "$LIMIT" \ --json number,title,url,author,isDraft,reviewDecision,createdAtMerge JSON arrays and dedupe by URL (or
repository + number). Single-repo runs can useunique_by(.number)if every PR URL shares one repo.Filter:
isDraft == falseandreviewDecision != "APPROVED".Present:
#, repo, author,reviewDecision, title, URL.
Example (replace AUTHORS, REPO, and host as needed):
export GH_HOST=github.enterprise.example.com # omit for github.com if default
authors=(alice bob carol) # from user or team API
REPO="acme/platform"
args=()
for a in "${authors[@]}"; do
f=$(mktemp)
gh pr list --repo "$REPO" --author "$a" --state open --limit 100 \
--json number,title,url,author,isDraft,reviewDecision,createdAt > "$f"
args+=("$f")
done
jq -s 'add | unique_by(.number) | map(select(.isDraft == false and .reviewDecision != "APPROVED")) | sort_by(.number)' "${args[@]}"
Multi-repo: outer loop for repo in "${repos[@]}"; do over the same author loop; dedupe by PR URL.
Mode B — All accessible repos (gh search prs)
Search returns PRs across repos visible to the user without listing each repo. You still need a second step for reviewDecision.
B1 — Accurate (recommended): search, then gh pr view
For each author, collect candidates:
gh search prs --author "$login" --state open --draft=false -L "$LIMIT" \ --json repository,number,title,url,author,isDraftMerge arrays from all authors:
jq -s 'add | unique_by(.url)' ...For each candidate:
jq -c '.[]' merged.json | while read -r row; do repo=$(echo "$row" | jq -r '.repository.nameWithOwner') num=$(echo "$row" | jq -r '.number') gh pr view "$num" --repo "$repo" \ --json number,title,url,author,isDraft,reviewDecision | jq 'select(.isDraft == false and .reviewDecision != "APPROVED")' doneDeduplicate if needed; present as a table.
Tradeoff: one gh pr view per candidate (fine for small queues; slow for many).
B2 — Approximate only (no per-PR view)
gh search prs supports --review {none|required|approved|changes_requested}. Not identical to reviewDecision. Use only if the user accepts approximation.
Mode C — Org-scoped search
Same as B, but add repeated --owner org flags:
gh search prs --author "$login" --state open --draft=false \
--owner acme-corp --owner acme-platform -L 100 \
--json repository,number,title,url,author,isDraft
Then apply B1 or B2.
GitHub / gh notes
reviewDecision == "": treat as “not fully approved”—keep those PRs.- Search indexing: results can lag slightly on some hosts.
- Caps: at most 1000 search hits per query.
- Noise: global search can include repos the team does not own; org-scoped search or
REPOSkeeps lists relevant.