How Smith Drives a PR Without Waiting

Why the bot never sits idle watching CI — and what actually woke it up on PR #5738.

The big idea: Smith never waits. Each time it works, it does one short turn, leaves a note saying "wake me when something happens to this PR," and goes to sleep. GitHub itself rings the doorbell (a webhook) when the review posts, CI finishes, or the PR merges. There is no Claude "stop hook" and no push-trigger — the wake-up signal comes from GitHub events.

The loop, in one picture

1 · Smith opens a PR writes code, pushes, opens PR 2 · Leaves a note "watch PR #5738 for me" 3 · Goes to sleep turn ends, nothing runs 4 · Something happens review posted · CI finished · PR merged webhook 5 · Doorbell rings listener on the mini checks: is there a note for this PR? yes ↓ 6 · Smith wakes up in the same Slack thread, with full memory of the PR 7 · Does ONE step fix a finding, re-check, or report re-leaves the note, sleeps again — repeat
Smith (awake) GitHub / plumbing Nothing running

The same loop, in words

  1. Smith opens the PR and immediately registers a tiny "watch" file — one line saying which PR and which Slack thread it belongs to.
  2. Smith's turn ends. Nothing is polling, nothing is burning compute. The watch file is just a note on a shelf.
  3. GitHub fires a webhook whenever one of three things happens to that PR: a review is posted, a CI check finishes, or the PR is merged.
  4. A small listener on the mini receives the webhook, sees the watch note, and injects a synthetic message into the PR's Slack thread — exactly as if a person had typed there.
  5. Smith wakes in that thread with its memory of the PR intact (session resume), does the one next step — address a review finding, confirm checks are green, announce mergeable — then re-registers the watch and sleeps again.
  6. When the merged event arrives, the watch note is thrown away. Loop over.

So what is NOT the trigger?

GuessReality
Claude stop hook?No. Stop hooks fire when a Claude session ends. Nothing here keys off that — the wake signal comes from GitHub, not from Claude.
A git push?Only indirectly. A push starts CI; when CI finishes, that completion event fires the webhook. The push itself wakes nobody.
A timer / cron?Not for the main loop. But there IS a separate safety-net timer — see below.

The safety net: the dead-review watchdog

Webhooks are great when the thing you're waiting for actually happens. But on #5738 the review bot simply never ran — so there was no event to wake anyone. That's what the watchdog is for: a scheduled job (a timer, this one really is a timer) that scans open PRs and asks, "has any PR been sitting too long with no review verdict?" If yes, it posts the alert you saw — "PR #5738 has waited 82 minutes with no review verdict" — with the exact re-fire command.

Belt and suspenders: the webhook handles the normal case instantly; the watchdog catches the case where the expected event never comes.

What actually happened on #5738

Quick FAQ

Why not just have Smith wait and poll?
Each Smith turn is a single short-lived process with a hard time budget. Sitting in a wait loop would burn the whole budget doing nothing — and get killed anyway. Registering a watch and sleeping costs zero.
How does Smith remember the PR when it wakes up?
The wake-up message lands in the same Slack thread, and Smith resumes the same session — so it has the full history of what it was doing.
Why doesn't the review bot re-run on every push?
By design: only opening, reopening, a manual dispatch, or an @claude review comment trigger it. That keeps costs down — but it's also the gap the watchdog exists to catch.

Code: agent-smith/src/agent_smith/pr_watch.py (watch registry) · pr_event.py (webhook → Temporal bridge) · scripts/review-webhook/listener.js (the doorbell) · design doc docs/planning/smith-pr-webhook-driven.md.

PropFlow Docs