# Pull Request Re-Review Gap Ledger (`kingii98/pull-request-re-review-gap-ledger`) Actor

Finds each open GitHub pull request where a reviewer already reviewed, the author then pushed new commits, and nobody requested a new review. One dataset record for each pull request and reviewer pair, plus one summary for each reviewer. HTTP only, no bro

- **URL**: https://apify.com/kingii98/pull-request-re-review-gap-ledger.md
- **Developed by:** [kingii98](https://apify.com/kingii98) (community)
- **Stats:** 2 total users, 1 monthly users, 100.0% runs succeeded, 0 bookmarks
- **User rating**: No ratings yet

## Pricing

from $10.00 / 1,000 run starteds

This Actor is paid per event. You are not charged for the Apify platform usage, but only a fixed price for specific events.

Learn more: https://docs.apify.com/actors/running/actors-in-store.md#pay-per-event

## What's an Apify Actor?

Actors are web data automations that power AI and operations. They run on the Apify platform to scrape websites, process data, connect APIs, and automate workflows.
In Batch mode, an Actor accepts a well-defined JSON input, performs an action which can take anything from a few seconds to a few hours,
and optionally produces a well-defined JSON output, datasets with results, or files in key-value store.
In Standby mode, an Actor provides a web server which can be used as a website, API, or an MCP server.
Actors are written with capital "A".

## How to integrate an Actor?

If asked about integration, you help developers integrate Actors into their projects.
You adapt to their stack and deliver integrations that are safe, well-documented, and production-ready.
The best way to integrate Actors is as follows.

- **AI agents and MCP clients** — the [Apify MCP server](https://docs.apify.com/integrations/mcp.md) at `https://mcp.apify.com` (remote, streamable HTTP, OAuth on first use).
- **Agentic workflows and local Actor development** — [Agent Skills](https://apify.com/.well-known/agent-skills/index.json) with the [Apify CLI](https://docs.apify.com/cli/docs.md): `npm install -g apify-cli`, then `apify login`.
- **JavaScript/TypeScript projects** — the official [JS/TS client](https://docs.apify.com/api/client/js/docs.md): `npm install apify-client`.
- **Python projects** — the official [Python client](https://docs.apify.com/api/client/python/docs.md): `pip install apify-client`.
- **Any other language** — the [REST API](https://docs.apify.com/api/v2.md).

For usage examples, see the [API](#api) section below.

For more details, see Apify documentation as [Markdown index](https://docs.apify.com/llms.txt) and [Markdown full-text](https://docs.apify.com/llms-full.txt).

# README

## Pull Request Re-Review Gap Ledger

Find each open GitHub pull request where a reviewer already reviewed, the author then pushed new commits, and nobody requested a new review.

GitHub removes a reviewer from the review request list when the reviewer submits a review. When the author then pushes a fix and does not request a new review, the pull request is in no "review requested" view, but the reviewer must still act. This Actor finds these pull requests and tells you how long each one waits.

### How it works

1. The Actor reads the open pull requests of each repository, the most recently updated first.
2. It skips each pull request with no activity for more than `staleDays` days.
3. For each other pull request, it reads the reviews, the commits and the open review requests.
4. For each reviewer, it takes the last submitted review. It then compares that time with the commits that came after it.
5. It writes one record for each pull request and reviewer pair, one summary for each reviewer, and one run summary.

The Actor keeps no state. Each run reads the full history again and computes each verdict again.

### Verdicts

| Verdict | Meaning |
| --- | --- |
| `awaiting-re-review` | The author pushed commits after the last review of the reviewer, and nobody requested a new review. This is the gap. |
| `requested` | The reviewer is on the review request list again. The pull request shows in the normal "review requested" view. |
| `up-to-date` | No commit came after the last review of the reviewer. |
| `stale-skipped` | The pull request had no activity for more than `staleDays` days. The Actor did not read its reviews. |

Rules:

- A pending (draft) review does not count. A review that the author of the pull request wrote does not count.
- A commit by the reviewer does not need a re-review, so the Actor ignores it.
- An approval followed by new commits is `awaiting-re-review`, because the approval is for old code.
- A review request to a team does not name a user. It does not set `reReviewRequested`.
- Bot reviews are ignored unless `includeBots` is true.

### Input

| Field | Default | Description |
| --- | --- | --- |
| `repositories` | `["pallets/click"]` | 1 to 20 repositories as owner/name. |
| `githubToken` | none | A fine-grained token that you own, with read access to pull requests and metadata only. Secret. |
| `reviewers` | `[]` | Optional GitHub logins. An empty list reports all reviewers. |
| `staleDays` | `90` | Skip a pull request with no activity for more days than this value. |
| `maxPullRequestsPerRepository` | `200` | Read at most this number of open pull requests from each repository (1 to 200). |
| `includeBots` | `false` | Also report reviews that bot accounts wrote. |
| `timeoutSeconds` | `20` | Timeout for one GitHub API call. |
| `maxResponseBytes` | `4000000` | Hard cap on the bytes of one GitHub API answer. |

#### With a token and without a token

- **With `githubToken`:** the Actor uses the GitHub GraphQL API. One call reads 10 pull requests with their last 100 reviews, their last 100 commits and their review requests. One call costs about 1 point of the hourly budget of 5,000 points. The Actor stops while 100 points are still free and reports a partial result. The Actor can read private repositories that the token can read.
- **Without `githubToken`:** GraphQL is not available, so the Actor uses the anonymous REST API on public repositories only. It sends one call for each page of 100 pull requests, then two calls (reviews and commits) for each pull request that is not stale. The anonymous limit is 60 calls for each hour, and on the Apify platform other users can share it. The Actor stops before it spends that budget and reports a partial result.

The Actor sends the token only to `api.github.com`, in the `Authorization` header. It never writes the token to the log or to the dataset. It never changes anything on GitHub.

### Output

The dataset holds three types of record. Use `recordType` to select them.

#### `pull-request-reviewer` (one for each pull request and reviewer pair)

| Field | Description |
| --- | --- |
| `repository` | owner/name. |
| `number` | Pull request number. |
| `title` | Pull request title. |
| `url` | Link to the pull request. |
| `author` | Login of the pull request author. |
| `isDraft` | True for a draft pull request. |
| `reviewer` | Login of the reviewer. It is null on a `stale-skipped` record. |
| `lastReviewState` | State of the last review of the reviewer: `APPROVED`, `CHANGES_REQUESTED`, `COMMENTED` or `DISMISSED`. |
| `lastReviewAt` | Time of that review. |
| `lastCommitAfterReviewAt` | Time of the last commit after that review. |
| `commitsAfterReview` | Number of commits after that review. |
| `reReviewRequested` | True when the reviewer is on the review request list now. |
| `hoursWaiting` | Hours since the first commit after the last review. |
| `verdict` | `awaiting-re-review`, `requested`, `up-to-date` or `stale-skipped`. |
| `prUpdatedAt` | Last activity on the pull request. |
| `note` | The reason for a `stale-skipped` verdict. |
| `checkedAt` | Time of the run. |

#### `reviewer-summary` (one for each reviewer)

`reviewer`, `awaitingReReview` (count of `awaiting-re-review` pull requests), `requested`, `upToDate`, `longestWaitHours`, `awaitingPullRequests` (for example `acme/web#12`, the longest wait first) and `checkedAt`. The reviewers with the most gaps come first. Send this record to a chat channel with an Apify integration.

#### `run-summary` (one for each run)

`runStatus` (`complete` or `partial`), `stopReason` (`charge-limit`, `rate-budget`, `token-rejected` or null), `mode` (`graphql` or `rest-anonymous`), the counts `pullRequestsListed`, `pullRequestsEvaluated`, `staleSkipped`, `pairs`, `awaitingReReview`, `reviewersWithGaps`, `longestWaitHours`, `apiRequestsSent`, one status record for each repository, a `note` and `checkedAt`.

A repository that cannot be read (for example 404) gets a status in the run summary, and the other repositories continue. A gap, an unreadable repository, a refused token and a spent rate budget are results. They never make the run fail.

### Pricing (pay per event)

| Event | Price | When the Actor charges it |
| --- | --- | --- |
| `run-started` | USD 0.01 | Once for each run, after the input is valid. |
| `pull-request-evaluated` | USD 0.002 | Once for each open pull request that the Actor read and evaluated for all its reviewers. |

- The Actor charges for each pull request, not for each reviewer pair. A large review team does not pay more for one pull request.
- A `stale-skipped` pull request and a pull request that could not be read are not charged.
- Before it reads the repositories, the Actor calculates how many pull requests your maximum total charge allows. At that number it stops and reports a partial result with `stopReason` `charge-limit`. The default cap of USD 2 covers about 995 pull requests.
- GitHub API calls use your own token and cost you nothing inside its hourly budget.

### Schedule

Run the Actor on a weekday morning schedule, for example at 08:00 in the time zone of your team. Send the reviewer summaries to a chat channel with an Apify integration. Open pull requests change each day, so the list is new each morning.

### Limits

- 20 repositories and 200 open pull requests for each repository in one run.
- The Actor reads the last 100 reviews and the last 100 commits of each pull request (GraphQL), or the first and the last page of 100 (REST). A reviewer whose last review is older than that is not seen.
- A commit time is the committer date. A rebase changes that date, so a rebased pull request shows as new commits.
- The Actor does not follow redirects. A renamed repository gives an error. Use the new name.

### Local development

```bash
uv sync
uv run pytest
uv run ruff check .
```

# Actor input Schema

## `repositories` (type: `array`):

1 to 20 GitHub repositories as owner/name, for example "pallets/click". The Actor reads the open pull requests of each repository, the most recently updated first.

## `githubToken` (type: `string`):

A fine-grained GitHub token that you own, with read access to pull requests and metadata only. With a token, the Actor reads each repository with the GitHub GraphQL API and can read private repositories. Without a token, the Actor reads public repositories only, with the anonymous REST API limit of 60 requests for each hour. The Actor sends the token only to api.github.com and never writes it to the log or to the dataset.

## `reviewers` (type: `array`):

Optional GitHub logins. When the list is empty, the Actor reports all reviewers. The match ignores upper and lower case.

## `staleDays` (type: `integer`):

The Actor skips a pull request with no activity for more days than this value. A skipped pull request gets one record with the verdict stale-skipped and is not charged.

## `maxPullRequestsPerRepository` (type: `integer`):

The Actor reads at most this number of open pull requests from each repository, the most recently updated first.

## `includeBots` (type: `boolean`):

Report reviews that bot accounts wrote, for example automatic code review apps. The default ignores them, because a bot does not wait for a re-review request.

## `timeoutSeconds` (type: `integer`):

Timeout for one call to the GitHub API.

## `maxResponseBytes` (type: `integer`):

Hard cap on the bytes read from one GitHub API answer. A larger answer is reported as an error for that repository.

## Actor input object example

```json
{
  "repositories": [
    "pallets/click"
  ],
  "reviewers": [],
  "staleDays": 90,
  "maxPullRequestsPerRepository": 200,
  "includeBots": false,
  "timeoutSeconds": 20,
  "maxResponseBytes": 4000000
}
```

# Actor output Schema

## `dataset` (type: `string`):

No description

# API

You can run this Actor programmatically using our API. Below are code examples in JavaScript, Python, and CLI, as well as the OpenAPI specification and MCP server setup.

## JavaScript example

```javascript
import { ApifyClient } from 'apify-client';

// Initialize the ApifyClient with your Apify API token
// Replace the '<YOUR_API_TOKEN>' with your token
const client = new ApifyClient({
    token: '<YOUR_API_TOKEN>',
});

// Prepare Actor input
const input = {
    "repositories": [
        "pallets/click"
    ],
    "reviewers": [],
    "staleDays": 90,
    "maxPullRequestsPerRepository": 200,
    "includeBots": false,
    "timeoutSeconds": 20,
    "maxResponseBytes": 4000000
};

// Run the Actor and wait for it to finish
const run = await client.actor("kingii98/pull-request-re-review-gap-ledger").call(input);

// Fetch and print Actor results from the run's dataset (if any)
console.log('Results from dataset');
console.log(`💾 Check your data here: https://console.apify.com/storage/datasets/${run.defaultDatasetId}`);
const { items } = await client.dataset(run.defaultDatasetId).listItems();
items.forEach((item) => {
    console.dir(item);
});

// 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/js/docs

```

## Python example

```python
from apify_client import ApifyClient

# Initialize the ApifyClient with your Apify API token
# Replace '<YOUR_API_TOKEN>' with your token.
client = ApifyClient("<YOUR_API_TOKEN>")

# Prepare the Actor input
run_input = {
    "repositories": ["pallets/click"],
    "reviewers": [],
    "staleDays": 90,
    "maxPullRequestsPerRepository": 200,
    "includeBots": False,
    "timeoutSeconds": 20,
    "maxResponseBytes": 4000000,
}

# Run the Actor and wait for it to finish
run = client.actor("kingii98/pull-request-re-review-gap-ledger").call(run_input=run_input)

# Fetch and print Actor results from the run's dataset (if there are any)
print(f"💾 Check your data here: https://console.apify.com/storage/datasets/{run.default_dataset_id}")
for item in client.dataset(run.default_dataset_id).iterate_items():
    print(item)

# 📚 Want to learn more 📖? Go to → https://docs.apify.com/api/client/python/docs/quick-start

```

## CLI example

```bash
echo '{
  "repositories": [
    "pallets/click"
  ],
  "reviewers": [],
  "staleDays": 90,
  "maxPullRequestsPerRepository": 200,
  "includeBots": false,
  "timeoutSeconds": 20,
  "maxResponseBytes": 4000000
}' |
apify call kingii98/pull-request-re-review-gap-ledger --silent --output-dataset

```

## MCP server setup

```json
{
    "mcpServers": {
        "apify": {
            "type": "http",
            "url": "https://mcp.apify.com/?tools=fetch-actor-details,kingii98/pull-request-re-review-gap-ledger"
        }
    }
}
```

The hosted server signs you in with OAuth on first connect, so no API token belongs in this config. Clients without OAuth support can send an `Authorization: Bearer <APIFY_API_TOKEN>` header instead, using a token from API & Integrations in Apify Console (https://console.apify.com/settings/integrations).

## OpenAPI specification

Download the OpenAPI definition: https://api.apify.com/v2/actors/igECjvlxEwcPcfClP/builds/3o9X5AILMA0OZLQLd/openapi.json
