Skip to main content

prhub

One GitHub poller for many coding agents.

CI OpenSSF Scorecard PyPI Codecov CodeRabbit reviews License: MIT

Coding agents (Claude Code, Codex, Cursor, …) spend a lot of their time waiting: for CI on the PR they just pushed, for a review, for a merge, for the deploy. Left alone, each one waits the same way, with its own polling loop:

gh pr checks 2814 --watch
until gh pr view 2814 --json state | grep -q MERGED; do sleep 30; done
gh run watch 123456

Every one of those loops spends the same budget: the 5,000 requests per hour of the one GitHub token they all share. With a dozen agents the budget is gone within the hour, and then every gh command fails for everyone, you included, until it resets. On the author's machine, a single agent's monitoring loop once made about 190 GraphQL calls a minute and emptied the shared budget in under half an hour.

prhub replaces all of those loops with one daemon per machine. Once a minute it reads every PR and commit anyone is waiting on in a single batched GraphQL request, and writes the state to local files. Agents block on those files with prhub wait, which never calls GitHub.

prhub wait api#2814 --until done                  # open, approved at the current head, CI green, mergeable
prhub wait api#2814 --until checks --timeout 1800 # CI finished, whatever the result
prhub wait acme/api@3a1e48e --until deployed      # this commit is live in production
prhub state https://github.com/acme/api/pull/42   # current state, instantly
prhub list --ready                                # your PRs that are ready to merge, right now
prhub status                                      # daemon health and the GitHub budget left

How it works

                 ┌──────────────── one machine ────────────────┐
  GitHub API ◄── │ prhub daemon ──► ~/.local/state/prhub/*.json │ ◄── prhub wait / state / list
  (1 batched     │   every 60 s                                 │     (any number of agents,
   query/min)    └──────────────────────────────────────────────┘      no API calls)
  • Discovery. Every few minutes the daemon runs one search for your open PRs (is:pr is:open author:@me, or your own query), so agents don't have to register anything.
  • Polling. Every minute, batched GraphQL requests read every open tracked PR (20 per request) and every watched commit (5 per request), together with each repo's recent deployments. Settled items are read less often: a merged or closed PR every 10 minutes and a commit whose runs have finished every 5, unless someone asks about them. With 30 to 40 open PRs that costs about 1 point per poll on the author's machine, roughly 60 to 120 points an hour, however many agents are waiting.
  • Waiting. prhub wait tracks whatever it is asked about, nudges the daemon for an early poll (at most one every 15 s), then reads local files until the condition holds. Only data fetched after the wait started is judged, so a stale file never ends a wait.
  • Backing off. Below 1,000 points left the daemon slows to every 3 minutes, below 300 to every 10, and when GitHub rate-limits it, it waits for the reset. During a rate limit prhub status says PAUSED, and waits keep waiting rather than failing, so agents don't fall back to polling by hand against the same exhausted limit.
  • Riding out GitHub errors. PRs are read 20 per request and commits 5. When GitHub times out on a request (a 502/504, or its "Something went wrong" on a heavy query), the daemon splits the batch and retries, and a PR or commit that still fails keeps its last state until the next poll. If GitHub keeps failing, prhub status says DEGRADED and waits keep waiting on the last good state, as during a rate limit.

Install

prhub is a single binary, written in Rust. It needs the GitHub CLI, logged in (gh auth login): it reads the token from gh auth token, keeps it in memory only, and needs nothing else.

uv tool install prhub            # prebuilt for macOS and Linux; or: pipx install prhub
prhub install-launchd            # macOS: run the daemon at login
prhub install-systemd            # Linux: run it as a systemd user service
prhub status                     # "daemon: healthy" within a minute

Without Python: download prhub-<version>-<platform>.tar.gz from the releases (each release carries SLSA provenance, prhub.intoto.jsonl), or build it with cargo install --git https://github.com/danielnc/prhub --locked.

To try it without installing a service, run prhub daemon in a terminal. Uninstall: on macOS, launchctl bootout gui/$(id -u)/dev.prhub.daemon and delete ~/Library/LaunchAgents/dev.prhub.daemon.plist; on Linux, systemctl --user disable --now prhub and delete ~/.config/systemd/user/prhub.service.

Pull requests

prhub wait <pr> --until <condition>[,<condition>…] returns as soon as any one condition holds.

condition met when
done open, not a draft, CI SUCCESS, merge state CLEAN (configurable), and an APPROVED review of the current head that is newer than the head commit
green the CI rollup is SUCCESS and no workflow run on the head is still waiting for its jobs
checks CI finished, whatever the result
merged the PR was merged
closed the PR was merged or closed
review a new review arrived after the wait started
change anything relevant changed: state, draft, CI, review, merge state, head commit

done deliberately does not trust GitHub's reviewDecision alone: it stays APPROVED after a new push unless the repository dismisses stale reviews, and a force-push with an unchanged patch relabels an old review onto the new head. prhub state marks such a PR review=APPROVED(stale: no approval of this head).

When CI goes red, state and wait also print the names of the failing checks.

Commits: CI and deploys

The same daemon watches commits, in any repository, so agents stop polling gh run watch and the Actions API.

prhub wait acme/api@3a1e48e --until green                      # every CI run on the commit passed
prhub wait api@3a1e48e --until green --workflow deploy         # only runs whose name contains "deploy"
prhub wait https://github.com/acme/api/commit/3a1e48e --until deployed --env production
prhub state @3a1e48e                                           # a commit of the clone you are in
condition met when
checks every run on the commit finished, whatever the result
green every run passed (exits 3 as soon as one fails)
deployed the commit is live in --env (default production, or any). Use the repository's environment name; production also matches prod/prd, staging matches stage/stg, development matches dev. If the repository never deploys to that name, wait says which environments it saw
change anything about the commit's runs or deploys changed

It works the same whatever a repository's workflow names, default branch or release style:

  • CI is every workflow run on the commit, including one whose jobs GitHub has not created yet, and every other check suite that ran something. Suites from apps that never start a run (they sit QUEUED forever) and cron-triggered runs (they attach to whatever the branch head is) are left out. A run cancelled because a newer run of the same workflow replaced it is not a failure. A run cancelled before any of its jobs started (its concurrency group replaced it while it waited) is not a failure either: it shows as NOT STARTED. When no other run of that workflow started on the commit, green can never be met for it, because that workflow never ran there; wait on the newer commit or use --until deployed. A dispatched run (workflow_dispatch, repository_dispatch) cancelled before it started is left out, like cron. Other cancelled runs count as failures and are listed as name (cancelled). A commit with more than 50 check suites stays PENDING, since prhub reads only the first 50 and an unread one may still be running.
  • Deployed reads GitHub Deployments, plus commit statuses named like deployed/<env>. A successful deploy of a later commit counts when that commit contains yours (asked once per new deployment through the compare API), so nightly releases, batched deploys and a cancelled deploy followed by a newer one all answer correctly. A workflow makes one Deployment per job that names the environment (build, migrate, deploy…), and GitHub marks each one successful when its job ends, so a Deployment made by a workflow run counts only once that whole run has finished successfully; until then the environment shows DEPLOYING. If a repository records no Deployments, use --until green --workflow <name of its deploy workflow>.

A commit is read while someone asks about it: 15 minutes after a state, and for as long as a wait runs plus 10 minutes after it ends. A poll reads at most 50 commits, those just asked about and those still running first, so a poll stays short however many commits are watched.

Naming things

you type means
https://github.com/acme/api/pull/42, acme/api#42 a PR
42 PR 42 of the clone in the current directory
api #42, api#42 a PR, by repo name or nickname (see below)
https://github.com/acme/api/commit/3a1e48e, acme/api@3a1e48e a commit (7 to 40 hex characters)
api@3a1e48e, @3a1e48e a commit, by nickname, or in the current clone

Nicknames come from [aliases] in the config, and from the names of the git clones found under clone_roots.

Exit codes

0 the condition was met · 1 timed out · 2 bad arguments, or no such commit · 3 the condition can never be met (the PR closed; for a commit, a run failed under green or its deploy failed) · 4 the daemon is not running.

Agent setup

Tell your agents. Add something like this to your agents' instructions (CLAUDE.md, AGENTS.md, …); a longer version is in docs/agents.md:

## Waiting on GitHub: use prhub, never poll
Every agent on this machine shares one GitHub token. To wait for a PR, CI or a deploy, run
`prhub wait <pr or owner/repo@sha> --until <condition>` (in the background if you need to keep working), and use
`prhub state <pr>` or `prhub list --ready` for the current state. These read local files and cost nothing.
Never loop over `gh pr checks`, `gh pr view`, `gh run watch` or `gh api` with `sleep`.
If `prhub status` says PAUSED, that is a GitHub rate limit: keep waiting, don't poll by hand.

Enforce it (Claude Code). prhub hook claude is a PreToolUse hook that refuses gh pr checks --watch, gh run watch, watch … gh … and shell loops that sleep around gh pr, gh run or gh api, and tells the agent which prhub wait to use instead. One-off reads and every write pass through untouched, and the hook stands aside entirely when the daemon is not healthy. Add it to ~/.claude/settings.json:

{ "hooks": { "PreToolUse": [ { "matcher": "Bash",
    "hooks": [ { "type": "command", "command": "prhub hook claude", "timeout": 5 } ] } ] } }

Put PRHUB_ALLOW=1 anywhere in a command to let one loop through on purpose.

Configuration

~/.config/prhub/config.toml (override the path with PRHUB_CONFIG). Every key is optional:

search = "is:pr is:open author:@me org:acme"  # what the daemon discovers by itself
interval = 60                                 # seconds between polls
discover_every = 180                          # seconds between discovery searches
clone_roots = ["~/code"]                      # clones under here become repo nicknames

[aliases]
api = "acme/api"                              # nickname -> owner/repo, or the name of a local clone
web = "acme/web-app"

[done]
merge_states = ["CLEAN", "HAS_HOOKS"]         # merge states that count as mergeable for `done`

Any PR or commit an agent waits on is tracked automatically, even if the search would not find it (someone else's PR, another organization). Closed PRs drop out an hour after they close.

Files

Everything lives in ~/.local/state/prhub (override with PRHUB_HOME):

path what
prs/<owner>__<repo>__<n>.json one record per PR: its summary (state, checks, reviews, merge state) and failing checks; deleted a day after the PR closes
commits/<owner>__<repo>__<sha>.json one record per watched commit
watch.json, commit-watch.json what agents asked to track, and who asked (PRHUB_CALLER, else a tmux or Herdr pane id)
events.jsonl every state change, one JSON line each; past 20 MB it moves to events.jsonl.1
runs.json deploy runs of later commits already asked about, shared by every commit they may contain
daemon.json, daemon.log daemon health, the GitHub budget left, and the log

Other tools can read these too. A dashboard, a merge queue or a status bar can use them without calling GitHub.

Limits

  • github.com only for now; GitHub Enterprise Server is not supported.
  • One daemon per machine and per GitHub token. Agents on other machines need their own daemon.
  • The state is up to one poll interval old (60 s by default). prhub state --fresh asks for a poll first.
  • done checks the review decision, CI and merge state that GitHub reports. If your merge rules need more (a specific reviewer, resolved threads), check those before merging.

Contributing

Bug reports and pull requests are welcome: see CONTRIBUTING.md. Report security issues privately, as described in SECURITY.md.

License

MIT

Metadata

Release files for prhub 0.2.1

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for prhub 0.2.1
File Size Uploaded
prhub-0.2.1.tar.gz 89.2 kB Details

Built distributions (wheels)

Table of built distributions (wheels) for prhub 0.2.1
File
prhub-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl Python 3 none Linux glibc 2.17+ x86-64 Details
prhub-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl Python 3 none Linux glibc 2.17+ ARM64 Details
prhub-0.2.1-py3-none-macosx_11_0_arm64.whl Python 3 none macOS 11.0+ ARM64 Details
prhub-0.2.1-py3-none-macosx_10_12_x86_64.whl Python 3 none macOS 10.12+ x86-64 Details
prhub-0.2.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl Python 3 none macOS 11.0+ ARM64, macOS 10.12+ universal2 (ARM64, x86-64), macOS 10.12+ x86-64 Details

Total release size: 13.5 MB

Release files / prhub-0.2.1.tar.gz

Download URL prhub-0.2.1.tar.gz
Size 89.2 kB
Tags Source
SHA-256 checksum
How to use checksums
ba74ce2d6ec1a2e9783424a7f2d1b6c15b912e4442b2697889d0bf62fa71b79d
BLAKE2b-256 checksum
How to use checksums
c386e606abb5a0f7b0a558e48003acaebfd0317c043a8d7f6767f44984390742
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / prhub-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl

Download URL prhub-0.2.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
Size 2.4 MB
Tags Linux glibc 2.17+ x86-64 Python 3
SHA-256 checksum
How to use checksums
2e91feac93ac8f38b70cf208050a7d35a39a1b345bb15e21e050ecb4368c09d7
BLAKE2b-256 checksum
How to use checksums
84577002b8099c653c6d7bce8c3d1449bab746ed7f936f105fc72ecfc2e4ac82
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / prhub-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl

Download URL prhub-0.2.1-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl
Size 2.2 MB
Tags Linux glibc 2.17+ ARM64 Python 3
SHA-256 checksum
How to use checksums
14a5e95aa41367a30ba25627c153cc1446ed44089af304e3c80460623395f17b
BLAKE2b-256 checksum
How to use checksums
6f8509932056f61c0b32e1232cee605299fc25e7bfd180e19dee20a5bdd5bbe9
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / prhub-0.2.1-py3-none-macosx_11_0_arm64.whl

Download URL prhub-0.2.1-py3-none-macosx_11_0_arm64.whl
Size 2.1 MB
Tags Python 3 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
389305351aa4b80a106dc90e79e7afde1137b73a4847e654f3fcdcce2a4f1527
BLAKE2b-256 checksum
How to use checksums
1afaa6370564c86909a5611cd15ae528628f501c2fd44a1ea3b7acf025f395bb
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / prhub-0.2.1-py3-none-macosx_10_12_x86_64.whl

Download URL prhub-0.2.1-py3-none-macosx_10_12_x86_64.whl
Size 2.3 MB
Tags Python 3 macOS 10.12+ x86-64
SHA-256 checksum
How to use checksums
d2eca2910417ffd90dd063dd6fb29fde00ac7272c747d1e8caac0513376645f0
BLAKE2b-256 checksum
How to use checksums
4492dc7ce0eeaa0f700ddd345b0298815b44e8c226bb275c239a059d897b482f
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release files / prhub-0.2.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl

Download URL prhub-0.2.1-py3-none-macosx_10_12_x86_64.macosx_11_0_arm64.macosx_10_12_universal2.whl
Size 4.4 MB
Tags Python 3 macOS 10.12+ universal2 (ARM64, x86-64) macOS 10.12+ x86-64 macOS 11.0+ ARM64
SHA-256 checksum
How to use checksums
0f3a20d70d816da6289d78fb2a4ce14d7919c1b6011367cedcc95da0db2af0e3
BLAKE2b-256 checksum
How to use checksums
1360a11e0faa988e72a9d59bd8831be90dfdefb57c846e5d338b5cf5f0a96cce
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.14

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Oct 16, 2026.

Transparency log

Release history Release notifications | RSS feed

This release

0.2.1 This release

6 release files

0.2.0

4 release files

0.1.6

2 release files

0.1.5

2 release files

0.1.4

2 release files

0.1.3

2 release files

0.1.2

2 release files

0.1.1

2 release files

0.1.0

2 release files

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page