Full context over streaming#
The obvious way to watch a PR is to stream it: poll checks with gh pr checks, watch a
run with gh run watch, tail logs as they come in. Shepherd doesn't do that, on purpose.
The real cost of a CI-only waiter#
A waiter that only watches checks has a blind spot: it can't see review comments until the run finishes, because it isn't looking. That's not just slower — it's wasteful. If a reviewer leaves a comment while CI is still running, a waiter finishes the run, reports green, and only then surfaces the comment. The agent fixes it, pushes, and retriggers the whole run — burning CI minutes on a build that was going to need a second pass anyway.
Shepherd's own operating instructions put it plainly:
Do not wait for CI to finish first with
gh pr checks,gh pr watch,gh run watch, or equivalent GitHub MCP check waiters — fetching check logs is fine, but those waiters only see CI and hide review comments until checks finish, which wastes CI when a later review fix retriggers the run.
One batch, not a fan-out#
Every iterate tick fetches PR state, review threads, comments, reviews, check runs, and
branch protection rules in one GraphQL batch query. When a PR needs more than one page
of results, Shepherd pays for a slim, combined-cursor follow-up — not another full
snapshot:
A PR that needs another page of threads and checks pays one follow-up, not two full snapshots.
That single batch is also why an agent doesn't need a second GitHub round-trip to act:
One
iteratetick surfaces the fields below so the agent does not need a second GitHub fan-out to reconstruct PR state.
--debounce batches the tail end, too#
This part is specific to the shell poll dispatcher (pr-shepherd [PR] /
--until-terminal) — MCP iterate returns one tick immediately and has no debounce.
Full context isn't just about the read path — it's about not acting on a half-finished
picture. --debounce (default 1 minute) gives the poller a settle window after the first
sign of actionable work: if a review comment and a CI failure both land within that
window, it reports them together as one TRIAGE, instead of sending the agent back
twice.
This is also why debounce ticks defer writing "seen" markers until the window closes — late comments that arrive during the settle window are not marked seen before the agent-facing result is built, so nothing gets silently swallowed by the batching itself.
What this buys you#
- One
iteratecall replaces the multi-tool fan-out an agent would otherwise perform against GitHub MCP,gh, and raw GraphQL. - CI is not retriggered by a review fix that could have been batched with the original failure.
- Base-branch protection rules ride along in the same query — no extra round-trip to learn what's actually required to merge.