Send one Slack digest for GitHub pull requests awaiting review

A weekday Slack digest gives reviewers a short list of older GitHub pull requests still waiting on them, with links back to the code.

Automate.ax team6 min read

Integrations used in this example

Make review requests visible without another notification stream

Review requests can sit in GitHub while the team works in chat. A developer may miss the notification tab, and a generic channel feed makes it hard to see which requests still need attention.

This example checks one repository on weekdays and posts a digest of open, non-draft pull requests that still have requested people or teams after your chosen age. It links directly to each pull request and names those reviewers. GitHub remains the source of truth for review state.

The developer said they kept missing their team's review requests because they did not keep GitHub's notification tab open.Source: a developer's missed GitHub review requests

What you'll tell the agent

  • The GitHub owner and repository to inspect. The agent can find these from your authorized GitHub account.
  • How old an open review request must be before it appears and the Slack channel for the digest.
  • Account authorization for GitHub and Slack. Slack requires a paid workspace for the current Automate.ax connection.

The agent will create the project and ask you for these details. When Automate.ax asks you to connect an account, authorize the integrations you want this workflow to use.

The automation

The weekday check excludes drafts and pull requests with no requested reviewers. Each line links to the current pull request so reviewers can inspect the latest code and comments.

github-pr-review-digest.automation.ts
import { automation, onSchedule, t, transform } from "automate.ax"
import { github } from "automate.ax/github"
import { slack } from "automate.ax/slack"
import { z } from "zod"

export default automation(
  "Send a focused pull request review digest",
  {
    parameters: [
      { label: "GitHub owner", name: "owner", type: "text" },
      { label: "GitHub repository", name: "repository", type: "text" },
      { label: "Slack channel ID", name: "slackChannelId", type: "text" },
      { label: "Minimum age in hours", name: "minimumAgeHours", type: "text" },
    ],
  },
  ({ parameters }) => {
    const minimumAgeHours = z.coerce
      .number()
      .positive()
      .parse(parameters.minimumAgeHours)
    const tick = onSchedule({ schedule: "0 9 * * 1-5", timeZone: "UTC" })
    const pullRequests = github.listPullRequests({
      owner: parameters.owner,
      repository: parameters.repository,
      state: "open",
      perPage: 100,
    })

    const pending = transform(
      [pullRequests, tick],
      (requests, { scheduledAt }) => ({
        atLimit: requests.length === 100,
        lines: requests
          .filter(
            (request) =>
              !request.draft &&
              ((request.requested_reviewers?.length ?? 0) > 0 ||
                (request.requested_teams?.length ?? 0) > 0) &&
              scheduledAt.getTime() - Date.parse(request.created_at) >=
                minimumAgeHours * 3_600_000,
          )
          .map(
            (request) =>
              `• #${request.number} ${request.title} — ${request.html_url} — waiting for ${[
                ...(request.requested_reviewers ?? []).map(
                  (reviewer) => reviewer.login,
                ),
                ...(request.requested_teams ?? []).map(
                  (team) => `team:${team.slug}`,
                ),
              ].join(", ")}`,
          ),
      }),
    ).filter(({ lines, atLimit }) => lines.length > 0 || atLimit)

    slack.sendMessage({
      conversation: parameters.slackChannelId,
      text: t`Pull requests awaiting review (first 100 open PRs):\n${pending.lines.transform((lines) => lines.join("\n"))}\n${pending.atLimit.transform((atLimit) => (atLimit ? "The page limit was reached. Add pagination before treating this digest as complete." : ""))}`.transform(
        escapeSlackText,
      ),
      unfurlLinks: false,
    })
  },
)

/** Keeps provider text from becoming Slack mentions or control markup. */
function escapeSlackText(text: string) {
  return text
    .replaceAll("&", "&")
    .replaceAll("<", "&lt;")
    .replaceAll(">", "&gt;")
}

What a run looks like

Example input

PR #42 was opened two days ago, is ready for review, and still requests Alex. The age threshold is 24 hours.

Expected result

The next weekday digest includes PR #42 and Alex's GitHub handle. A draft PR and an already reviewed PR are omitted.

Set it up

  1. Copy the setup prompt at the top into your coding agent.
  2. Tell the setup agent which repository and Slack channel to use and what review age matters to your team.
  3. Authorize GitHub and a paid Slack workspace. Let the agent compare the first digest with the repository's open pull request list before leaving the schedule on.

Check the result

Use a test repository or a known open pull request with a requested reviewer. Run the automation and check its link, draft status, reviewer list, and age against GitHub.

Where this workflow stops

  • This version reads the first 100 open pull requests. If it reaches that limit, the Slack message warns that the digest may be incomplete. A larger repository needs pagination before deployment.
  • It uses pull request age as a simple threshold, not the time the latest reviewer was requested. Re-requesting a review does not reset that clock.
  • GitHub also offers built-in notifications and scheduled reminders. Use this when a team wants a shared, filtered digest with its own age rule.

Common questions

Will the digest ping reviewers directly?

No. It posts one message in the channel you choose. You can add a reviewed GitHub-to-Slack user map later if direct mentions are useful.

What happens after a reviewer submits a review?

GitHub removes a satisfied review request from the current requested-reviewer list. The pull request then leaves the next digest unless another reviewer is still requested.

More automation examples