---
name: github-stale-pull-requests
description: "Every Monday: open PRs with no commits, comments or reviews in 7 days, who each one waits on, and whether to nudge, merge or close it. Use when the user asks for this, or when it runs on a schedule."
---

# GitHub stale pull requests

Every Monday: open PRs with no commits, comments or reviews in 7 days, who each one waits on, and whether to nudge, merge or close it.

An idle pull request gets more expensive every week it sits: it drifts from main, its author moves on to something else, and the conflict at the end is bigger than the change. Most repos have a few, and nobody owns the list.

On Mondays the agent lists PRs nobody has touched in a week, works out whether each one waits on its author, a reviewer or whoever merges, and suggests a move. Drafts get their own short section, since an abandoned draft is a different conversation than a stuck review.

## Two ways to get the data

Use the API or MCP route when you have an MCP server, a CLI or an API token for the app. Otherwise, if you can drive a browser the user is signed in to (Claude in Chrome, a Playwright or Chrome DevTools MCP server), use the browser route. Say which one you used.

## Route A: API or MCP server

### What you need

- Read access to the repo with `gh` signed in, or a GitHub MCP server or connector.
- On private repos, a token that can read pull requests, their reviews and their checks.

If something is missing, say what and stop; do not invent numbers.

### Steps

1. **List idle PRs** (GitHub): `gh pr list --repo acme/acme-web --state open --search "updated:<2026-10-09" --json number,title,author,updatedAt,isDraft,reviewDecision,mergeable,reviewRequests --limit 100`, with the date set to 7 days ago.
2. **Work out who it waits on**: `reviewDecision: CHANGES_REQUESTED` waits on the author; `REVIEW_REQUIRED` with open `reviewRequests` waits on those reviewers; `APPROVED` waits on whoever merges. A `mergeable` value of `CONFLICTING` always puts it back on the author.
3. **Confirm it is really idle** (GitHub): `updatedAt` also moves on label changes and bot comments, so read `gh pr view <n> --json commits,comments,reviews` and use the newest commit, human comment or review as the last real activity.
4. **Suggest a move**: Nudge whoever it waits on when idle for 7 to 29 days, propose merging when approved with green checks, and propose closing when idle for 30 days or more with conflicts. The skill does not comment or close anything.
5. **Write the list**: Counts by who it waits on, then the PRs longest idle first with days idle and the suggested move. Drafts go last.

## Route B: the browser

### What you need

- Signed in to GitHub with read access to the repository, including its checks.

If something is missing, say what and stop; do not invent numbers.

### Steps

Start at https://github.com/pulls. The user is signed in already; never type passwords or codes, and ask if a sign-in page appears.

1. **List idle PRs**: In the search box, replace the query with `is:pr is:open draft:false repo:acme/acme-web updated:<2026-10-09 sort:updated-asc`, with the date set to 7 days ago. Run it again with `draft:true` for the drafts list. The icons at the right of each row show the review state and check status.
2. **Work out who it waits on**: Open each PR. The merge box reads **Changes requested** (waits on the author), **Review required** with names under **Reviewers** in the sidebar (waits on them) or **Approved** with green checks (waits on whoever merges). "This branch has conflicts that must be resolved" always puts it back on the author.
3. **Confirm it is really idle**: The `updated` qualifier also moves on labels and bot comments, so scroll the **Conversation** timeline and take the newest commit, human comment or review as the last real activity.
4. **Suggest a move**: Nudge whoever it waits on at 7 to 29 days idle, propose merging when approved with green checks, and propose closing at 30 days or more with conflicts. Do not comment on or close anything.
5. **Write the list**: Counts by who it waits on, then the PRs longest idle first with days idle and the suggested move, drafts last.

Note: The list shows 25 PRs per page. Add `-author:app/renovate -author:app/dependabot` to the query to leave bot PRs out.

## The report

- **Idle PRs**: the count, split by waiting on the author, reviewers or a merge.
- **The list**, longest idle first: number, title, author, days idle, who it waits on and the suggested move.
- **Approved and green** PRs nobody merged, called out on their own.
- **Drafts** idle for 7 days or more.

## Post it to Briefkin

When `BRIEFKIN_TOKEN` is set, or `~/.config/briefkin/env` exists (the installer from https://briefkin.dev/docs writes it), also send the result to the user's Briefkin board as one card. Keep the `item_key` the same on every run so the card updates in place, use a new `event_id` per run, and put today's real numbers in place of the example ones. If the request fails, say so once and carry on.

```bash
[ -n "${BRIEFKIN_TOKEN:-}" ] || . ~/.config/briefkin/env
curl -sS -X POST "${BRIEFKIN_URL:-https://briefkin.dev}/v1/events" \
  -H "authorization: Bearer $BRIEFKIN_TOKEN" \
  -H "content-type: application/json" \
  -d @- <<'JSON'
{
  "v": 1,
  "event_id": "github-stale-pull-requests/<date of this run, e.g. 2026-10-16>",
  "item_key": "github-stale-pull-requests",
  "project": "acme-web",
  "source": "GitHub stale pull requests skill",
  "kind": "update",
  "title": "9 pull requests idle for a week or more",
  "summary": "**4 wait on reviewers**, 3 on their authors, and 2 are approved with passing checks but not merged.",
  "widget": [
    {
      "type": "Table",
      "props": {
        "columns": [
          "PR",
          "Idle",
          "Waiting on",
          "Suggested"
        ],
        "align": [
          "left",
          "right",
          "left",
          "left"
        ],
        "rows": [
          [
            "#1188 Replace moment with date-fns",
            "34d",
            "Author, conflicts",
            "Close or rebase"
          ],
          [
            "#1231 Webhook signature v2",
            "15d",
            "@acme/security",
            "Nudge reviewers"
          ],
          [
            "#1240 Lazy-load dashboard charts",
            "12d",
            "Merge",
            "Merge: approved, green"
          ],
          [
            "#1246 Pagination on the audit log",
            "11d",
            "jlee",
            "Nudge reviewer"
          ],
          [
            "#1252 Cache org settings in KV",
            "9d",
            "Author",
            "Nudge author"
          ],
          [
            "#1255 Bump eslint to v9",
            "8d",
            "Merge",
            "Merge: approved, green"
          ],
          [
            "#1260 Retry failed exports",
            "8d",
            "mo",
            "Nudge reviewer"
          ],
          [
            "#1262 Remove the legacy billing flag",
            "7d",
            "@acme/billing",
            "Nudge reviewers"
          ],
          [
            "#1259 Keyboard nav in the org switcher",
            "7d",
            "Author",
            "Nudge author"
          ]
        ]
      }
    },
    {
      "type": "Callout",
      "props": {
        "text": "#1240 and #1255 only need someone to press merge.",
        "tone": "info"
      }
    }
  ]
}
JSON
```

Widgets come from the Briefkin catalog (https://briefkin.dev/v1/catalog). Without a token, just give the user the report.

## Run it on a schedule

Suggested: Mondays at 9:30 (`30 9 * * 1`). From cron, with the agent signed in on that machine:

```bash
30 9 * * 1 claude -p "Run the github-stale-pull-requests skill"
# Codex: codex exec, Gemini CLI: gemini -p, Copilot CLI: copilot -p, OpenCode: opencode run
```

Or use the agent's own scheduler (Claude Code routines, Codex automations, Claude or ChatGPT scheduled tasks): https://briefkin.dev/skills/github-stale-pull-requests#schedule

Source: https://briefkin.dev/skills/github-stale-pull-requests
