PRFlow

July 25, 2026 · 7 min read

Slack reminders for GitHub pull requests

You want Slack to nudge the right reviewer when a PR has been sitting. The default GitHub Slack app is too noisy for that, so here is a scheduled GitHub Action that does it in about 30 lines, plus an honest account of where the DIY version starts to hurt.

A pull request that nobody notices sits for days. The usual fix is a Slack ping, but the way most teams get GitHub events into Slack, the /github subscribe app, fires a message on nearly every action and never on the one thing you actually want: "this PR has been waiting on you for a day." This post builds that missing reminder yourself with a scheduled workflow, then shows the exact points where a hand-rolled version costs more than it saves.

The 60-second version

  • The default GitHub Slack app posts per event, so review requests drown in commit and comment noise.
  • GitHub's app does have scheduled reminders, but they are per channel or team on a fixed time, with no idle-time or CI awareness.
  • A cron-triggered GitHub Action can list open PRs, keep the ones with pending reviewers, and post a targeted Slack digest. Full code below.
  • The DIY version is stateless: it re-pings every run, can't edit or thread messages, and leans on updated_at as a rough proxy for "idle."
  • Once you want per-reviewer routing, dedupe, and CI status on the message, you're building a small service.

Why the default GitHub Slack app doesn't do this well

The official GitHub + Slack integration works by subscribing a channel to a repo with /github subscribe owner/repo. Out of the box that turns on issues, pulls, commits, releases, and deployments. The pulls feature notifies on PR changes and on draft PRs marked ready, so an active repo produces a steady stream: opened, each push, each review, each comment. The signal you care about, "reviewer X has not looked at PR #123 in a day," is buried in that stream.

To its credit, the app also has a scheduled reminders feature that posts pending review requests at set times. That is genuinely useful and worth turning on. Its ceiling is that reminders are coarse: they fire on a fixed schedule for a channel or team, they can't key off how long an individual review has been idle, and they carry no pipeline status. If you want "only ping when a PR has waited more than N hours, and tell me which check is red," you need your own logic.

The DIY version: a scheduled GitHub Action

The building blocks are all free. A scheduled workflow runs on cron, the REST pulls API lists open PRs with their requested_reviewers, draft, and updated_at fields, and a Slack incoming webhook posts the result. Store the webhook URL as a repository secret named SLACK_WEBHOOK_URL and drop this in as .github/workflows/review-reminders.yml:

name: Review reminders
on:
  schedule:
    - cron: "7 9 * * 1-5"   # 09:07 UTC, Mon-Fri (off-hour on purpose)
  workflow_dispatch:          # lets you run it by hand while testing

jobs:
  remind:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/github-script@v7
        env:
          SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }}
        with:
          script: |
            const { owner, repo } = context.repo;
            const prs = await github.paginate(github.rest.pulls.list, {
              owner, repo, state: "open", per_page: 100,
            });

            const IDLE_MS = 24 * 60 * 60 * 1000;   // 1 day
            const now = Date.now();
            const waiting = prs.filter(pr =>
              !pr.draft &&
              pr.requested_reviewers.length > 0 &&
              now - new Date(pr.updated_at).getTime() > IDLE_MS
            );
            if (waiting.length === 0) return;

            const lines = waiting.map(pr => {
              const who = pr.requested_reviewers.map(r => r.login).join(", ");
              return `<${pr.html_url}|#${pr.number} ${pr.title}> — waiting on ${who}`;
            });

            await fetch(process.env.SLACK_WEBHOOK_URL, {
              method: "POST",
              headers: { "Content-type": "application/json" },
              body: JSON.stringify({ text: `Pending reviews:\n${lines.join("\n")}` }),
            });

That's the whole thing. actions/github-script runs on Node 20, so fetch is built in and you get an authenticated Octokit as github for free. Slack's <url|label> syntax renders each PR as a clickable link. Run it once with the workflow_dispatch button to confirm the message shape before you trust the schedule.

Full disclosure: we make PRFlow, which does this without the workflow. If you'd rather not own a reminder script, PRFlow posts one Slack message per pull request that updates in place with CI/CD status and threaded comments, on GitHub and GitLab. The snippet above is a fine place to start; keep reading for where it stops being fine.

Where the DIY reminder breaks

None of these are reasons to avoid the script. They are the maintenance you're signing up for, and they show up in this order.

It re-pings on every run. The workflow has no memory. If a PR is still idle tomorrow, it posts again, and again the day after. There's no state store in Actions, so deduping ("only remind once per PR per day") means writing to a cache, a Gist, or an external store and reading it back. That's the first afternoon you lose.

updated_at is a lie for "idle." It changes on any activity, including a bot comment, a label, or a rebase push. So a PR that got a "please fix" review 20 hours ago but had a linter bot comment 5 minutes ago looks fresh and gets skipped. Real "waiting on reviewer" state needs the reviews and timeline APIs, not one timestamp.

It pings a channel, not a person. requested_reviewers gives you GitHub logins. To @-mention someone in Slack you need their Slack member ID, which means maintaining a GitHub-to-Slack lookup table and keeping it current as people join and leave. Miss one and the reminder is addressed to nobody.

Incoming webhooks can only create messages. Slack's own docs are explicit that incoming webhooks do not allow you to delete a message, and they can't edit or thread one either. So you can't keep a single updating "these PRs are waiting" message; you get a new post every run, which is its own kind of noise. Editing in place needs a full OAuth Slack app with chat.update and stored message timestamps.

The schedule itself is soft. Per GitHub's docs, scheduled workflows can be delayed during periods of high load, and the top of every hour is the worst slot. The shortest interval is once every 5 minutes, and in public repos the schedule auto-disables after 60 days without repo activity. A quiet repo silently stops reminding.

There's no CI status. The reminder says a PR is waiting but not whether it's even mergeable. Adding "and checks are green" means another API call per PR (the checks or commit-status endpoints) and more code to correlate.

Build it or skip it?

  • Build the Action if one repo, a channel-level ping, and "good enough" idle detection cover you. It's honest, free, and yours.
  • Turn on GitHub's scheduled reminders too — they're zero maintenance and complement the Action for the simple cases.
  • Reach for a maintained tool once you need per-reviewer routing, dedupe, one updating message, or CI status on that message. At that point you're maintaining a service, and running it yourself rarely pays off.

If the noise problem sounds familiar on the GitLab side too, the same trade-offs play out there — see every way to connect GitLab and Slack. And if reminders are a symptom of reviews piling up, the code review bottleneck is the underlying problem worth reading about. A close cousin of this post covers the other half of the noise problem: sending a Slack notification only when a PR is ready for review.

Bottom line

A scheduled GitHub Action is the right first move for reminders: it's a few lines, it's free, and it targets the exact PRs that are waiting. Just go in knowing you've adopted a small stateful problem in disguise — dedupe, reviewer mapping, and message editing are where the real time goes. Start with the script, add GitHub's built-in reminders, and switch to a maintained tool the moment "just one more field" turns your reminder into a service.

Frequently asked questions

Slack reminders for GitHub pull requests, answered

Can the GitHub Slack app remind reviewers about open pull requests?
Yes. The official GitHub + Slack integration has a scheduled reminders feature for pending review requests at set times. The limits are that reminders are configured per channel or per team on a fixed schedule, they cannot filter by how long a specific reviewer has been idle, and they carry no CI status. A scheduled GitHub Action gives you that finer control at the cost of maintaining it yourself.
How do I send a Slack reminder for GitHub pull requests without a paid tool?
Run a GitHub Action on a cron schedule that lists open pull requests with the REST API (GET /repos/OWNER/REPO/pulls), filters to non-draft PRs that still have requested_reviewers, and posts a digest to a Slack incoming webhook. It is free and works, but it is stateless, so it re-pings on every run unless you add your own dedupe, and it cannot edit or thread the messages it posts.
Why does my scheduled reminder workflow run late?
GitHub Actions scheduled workflows can be delayed during periods of high load, and the start of every hour is the worst time to schedule because that is when load spikes. The shortest interval is once every 5 minutes, and in public repositories scheduled workflows are automatically disabled after 60 days of no repository activity. Pick an off-hour minute like 09:07 and keep the repo active.

Tired of maintaining a reminder script?

PRFlow posts one Slack message per pull request that updates in place, with CI/CD status and threaded comments, on GitHub and GitLab. No cron, no reviewer lookup table, no re-ping spam.

Try PRFlow Free

Or read every way to connect GitLab and Slack first.