Watch Review Requests
Watch the authenticated user's inbound GitHub review requests for the current repository plus registered workspace repositories. For each newly actionable direct request, run the repository-qualified read-only /review-pr workflow exactly once, render its complete report, then pause for disposition. Process one review at a time so analysis, reports, and questions cannot interleave.
The bounded collector resolves repository scope inside each tick. It never falls back to an account-wide search: unavailable or empty local scope fails closed rather than searching the authenticated user's other GitHub repositories. Existing session state for repositories no longer in scope is discarded by the collector.
This watcher does not replace /review-pr. It composes the bounded queue collector and immutable
review contract. No external action is the default-safe outcome.
Usage
/watch-review-requests # adaptive; stop 18:00; at most 3 reviews
/watch-review-requests 10m 17 --reviews 5 # fixed 10m; stop 17:00; at most 5 reviews
/watch-review-requests reset # stop and clear session-local watcher state
/watch-review-requests recheck owner/repo#123 # one immediate selected recheck; no watcher start
/watch-review-requests disposition owner/repo#123 # reopen one deferred or saved draft
/watch-review-requests status # show runtime, queue, and budget state
Parse at most one positive \d+m interval, one stop hour from 0 through 23 (default 18),
and one --reviews N or --reviews=N budget from 1 through 20 (default 3). Reject unknown,
duplicate, missing, or malformed arguments. No interval means adaptive cadence. reset,
recheck owner/repo#123, disposition owner/repo#123, and status are user commands, not watcher
starts. Runtime-injected ticks use exactly tick adaptive|fixed --reviews N --stop-at ISO_8601;
accept those tokens only in internal tick mode and reject them on a normal start.
Resolve today's deadline in local time. Do not start at or past it. Before scheduling, verify that
the current route satisfies the premium tier; unlike a manual /review-pr, this attended watcher
must not fall back to reduced depth. If the route cannot be established, stop without scheduling.
Render this start preflight before the start call:
Premium route: {current premium route}
Cadence: {adaptive | fixed interval}
Stop deadline: {local timestamp}
Review budget: {N premium review attempts}
Safety boundary
Queue collection and review analysis are read-only. Never switch branches, create a checkout, fetch, edit code, change Git history, mark notifications, alter requested reviewers, submit a review, or send Slack while polling or analyzing. Never infer permission to communicate from a verdict or disposition category. Do not run shell, Git, filesystem, workspace, or authentication preflight probes when starting the watcher; the bounded tick collector resolves the current/workspace repository scope and reports its own failures.
A GitHub submission is permitted only through the separate confirmation sequence below, after showing the exact text and repository and a fresh immutable-state check. Slack is draft-only in this watcher; it has no Slack send permission. The safe response to every report is No external action. Never discover or guess a Slack recipient, channel, or workspace.
Session-local state
Carry structured state in this conversation; never write a cache, repository file, GitHub marker, notification marker, or Beads item. Retain:
- the collector's bounded queue state verbatim;
- run start/deadline, cadence, configured review budget, and a monotonic
reviewAttemptscount; - a per-run
completedWorkKeysset for exactly-once successful reports, reset withreviewAttemptson a new normal start and therefore capped by the 1–20 attempt budget; - at most 20 pending dispositions, keyed by repository, PR node, request event, and reviewed head,
each in
verified-unmarked,fresh,deferred,github-draft,slack-draft, orstalestate; - one bounded in-flight record with
workKey, phase (analyzing,review-complete,report-rendered, orverified-unmarked), and the complete automation result when available; - consecutive failure counts per collector/review source and adaptive quiet streak.
Restarting in the same session reuses handled queue work and pending dispositions, but creates a
new run deadline, resets reviewAttempts and completedWorkKeys, and creates a new count budget.
Queue handled keys still prevent replay. A new session announces a fresh baseline. If an active watch
loses state after context compaction, stop instead of resetting its budget or replaying work; a
manual new start may then announce a fresh baseline.
reset first stops an armed/running/paused watcher, then clears queue state, pending dispositions,
completed keys, review attempts, in-flight state, run deadline, cadence, budget, failure counts,
and quiet streak. It
does not mutate GitHub. status reports /watch-status when protocol v1 exists, the resolved repository scope, state size, pending
dispositions by state, current phase, failures, deadline, and budget. recheck owner/repo#123 makes
one bounded queue call with --recheck, selects only that qualified PR, and performs the serial
review flow below; a draft remains non-actionable. It does not schedule a watcher or treat other
recheck rows as new. disposition owner/repo#123 reopens one deferred or saved-draft record without
rerunning analysis. A GitHub draft record retains its exact kind, body, quoted heredoc command, and
immutable target; reopening resumes at Verify for submission. A Slack draft record retains its
exact paste-ready text and target; reopening shows Keep private or Keep draft only.
A deferred or saved-draft record remains listed but is not automatically prompted on later ticks.
It never blocks a different PR. A fresh disposition is offered before new work; after Defer it
moves to deferred, allowing the next queued PR to proceed serially. Resolve an older pending
record before starting another report for the same PR.
Start behavior
Normal invocations start an attended recurring watcher. The first tick lands after about one minute. Premium route, deadline, and review budget must be visible first.
Pi protocol v1
This section is Pi-only. In Claude Code, skip directly to Claude Code fallback without probing, searching for, or discussing Pi. Harness selection comes from the current tool surface; never use the shell to detect another harness or executable.
If the current harness directly exposes watch_loop, use this branch before Claude scheduling:
Call
watch_loopwithaction: statusand requireprotocolVersion: 1. If another watch isarmed,running, orpaused, do not replace it; show status and point to/watch-status,/watch-stop, or/watch-resume. Stop on a protocol mismatch.Convert the local deadline to ISO-8601 with its timezone offset for
stopAt.Use this self-contained tick prompt. Substitute every brace before starting; do not leave the tick dependent on prose from the initiating turn:
Load and follow the skill named `watch-review-requests` now in `tick` mode. This is one attended inbound-review tick, not a watcher start. Cadence is {cadence_mode}; the immutable run stop deadline is {deadline_iso}; the configured premium-review attempt budget is {review_budget}; derive the remaining review count from that budget minus the session-local reviewAttempts count. Preserve the session-local collector state, pending dispositions, completedWorkKeys, reviewAttempts, in-flight record, failures, and quiet streak. The premium route {premium_route} was established before launch; verify it still satisfies premium before invoking Skill(review-pr). If required state is unavailable, stop rather than resetting the budget or replaying work. Collect the bounded requested-review queue, use only the allowlisted gh-pr-checkout helper for optional local discovery, never run ad-hoc shell probes, process direct actionable work strictly one review at a time, render every complete immutable review before marking it locally reviewed, reverify it before every disposition stage, and wait for each answer. Never submit to GitHub without showing the exact draft and receiving the separate final confirmation followed by immutable verification; Slack is draft-only and never sends. An open question blocks the tick: do not call watch_loop complete while waiting. Do not start another premium invocation when the deadline or review budget is reached; always finish a retained or newly returned complete result through rendering, verification, local completion, and disposition, then stop. After visible output and the final next-tick line, call the matching protocol-v1 watch_loop action: complete. Use outcome: continue and adaptive delaySeconds from that line, or omit delaySeconds in fixed mode; use outcome: stop for a handoff, external send, deadline/budget exhaustion, lost state, or terminal failure.Adaptive start:
action: start protocolVersion: 1 label: Review requests mode: adaptive initialDelaySeconds: 60 missedCompletionPolicy: pause stopAt: <today's local deadline as ISO-8601> tickPrompt: <prompt above>Fixed start adds the selected interval in seconds:
action: start protocolVersion: 1 label: Review requests mode: fixed initialDelaySeconds: <interval seconds> intervalSeconds: <interval seconds> missedCompletionPolicy: pause stopAt: <today's local deadline as ISO-8601> tickPrompt: <prompt above>
The runtime clamps intervals to 60–3600 seconds. A successful start ends the initiating turn. The
pause policy is mandatory: an unanswered, interrupted, or malformed attended tick must never retry
itself. Only a completed tick may call action: complete.
Claude Code fallback
In Claude Code, enter this branch directly. Do not inspect the filesystem, PATH, process list, or
installed binaries to decide whether Pi exists, and do not include Pi capability commentary in the
start preflight. Use Claude's existing scheduling capability. If neither ScheduleWakeup nor
/loop is available, explain that recurring watches are unsupported and stop.
For adaptive mode, apply the established Fable session-model guard: Fable must not start an
adaptive watcher because its trailing scheduling call can discard visible output. Recommend a
Sonnet/Opus session or fixed mode. Otherwise schedule the first tick after 60
seconds with the same self-contained contract. Each completed adaptive tick renders first, waits
for every answer, and calls ScheduleWakeup last using the numeric N from next-tick:. After the terminal
next-tick: line, emit no more natural-language output; adaptive mode immediately calls
ScheduleWakeup, while fixed mode ends the turn. Never schedule while a question is open. Stop
instead of scheduling past the deadline or after a terminal outcome.
For fixed mode use:
/loop {interval} /watch-review-requests tick fixed --reviews {N} --stop-at {deadline_iso}
Substitute the resolved interval, budget, and deadline before launch. Fixed ticks ignore adaptive delay recommendations. Every fallback tick must load this skill, preserve session-local state, and obey the same serial and confirmation boundaries.
Tick mode
1. Enforce run bounds
Before collection and before each tool/model phase, compare local time with the run deadline and
check the remaining review count (budget - reviewAttempts). Do not begin another expensive
review unless at least 30 seconds and one attempt remain. Zero remaining attempts blocks only a
new Skill(review-pr) invocation: a retained or newly returned complete result must still be
persisted, rendered, verified, marked, and offered for disposition. A budget or deadline stop
preserves other unhandled queue work for a later start. A pending disposition may still be resolved
without spending another review count, but the watcher then stops.
Before collection or selection, recover in-flight state in this strict order:
verified-unmarked— reverify, then repeat the idempotent local-completion transaction.report-rendered— reverify the retained complete result, then continue local completion.review-complete— render the retained automation result; never invoke/review-pragain.analyzingwithout a complete result — treat the charged attempt as interrupted, clear only that phase, and retry later only if deadline and another attempt remain.
A complete retained automation result always wins over its older phase label. This recovery path prevents a second successful premium run even when interruption occurs before or during rendering.
2. Collect transitions once
Pass prior collector state as the bounded --state-json argument to one call:
~/.agents/skills/pr-status/scripts/gh-pr-review-requests.py \
--state-json 'PRIOR_STATE_JSON' --timeout REMAINING_COLLECTION_SECONDS
On the first tick omit --state-json. Preserve the returned state even when no review runs.
Inspect status, scope, errors, failedRepositories, transitions, and queue before acting. Require scope.kind to be current-workspace and reject any transition or queue row outside its repository list. Retain the full resolved list for status, scope changes, and failures; a healthy no-change tick shows only its repository count.
failed: show the bounded error, increment its failure streak, and do not review or mark work. A missing or empty current/workspace scope is a collector failure, never permission for a global fallback.partial: render every failed source and continue only with fully identified queue items.- After three consecutive failures for the same source, stop with retained state.
- Complete data clears only the matching failure streak.
Draft requests wait until their explicit ready transition; a qualified recheck does not override
the collector's draft exclusion. Render team requests separately as informational; never treat them as personal
direct requests. Render re_requested, head_changed, request removal, submitted review,
closed/merged, and draft/ready transitions explicitly. A new commit alone is not a re-request.
Healthy no-change ticks are user status, not diagnostics. When no pending disposition,
transition, or direct queue item needs attention, do not render a table, repository list,
collector status, transition count, internal state, or budget. Keep those details in session state
and show them only for status, a relevant transition, partial/failed collection, or another
condition that needs attention.
Render exactly one human-facing status line immediately before the required next-tick: protocol
line:
No new direct review requests across {repository_count} workspace repositories.
Do not show a next-check time or interval: fixed mode can ignore the adaptive delay in the
following next-tick: line, and scheduler execution can cross a minute boundary.
Do not add another completion summary. Do not add a bookkeeping explanation or task-tracking
commentary after the protocol line.
3. Select serial work
Sort actionable direct queue items by request-event time, then repository and PR number. Select
only the first item. Multiple requests remain queued and are processed serially after the current
report is resolved or deferred. Never process a team request. Use the queue workKey unchanged.
Offer a fresh pending disposition before spending budget on new work. Do not automatically offer
deferred, github-draft, or slack-draft records; list them in the summary and let
disposition owner/repo#123 reopen one. They do not block a different PR. If any unresolved record
already belongs to the selected PR, skip that PR until its disposition is resolved. When 20 pending
dispositions are retained, do not start or mark another review; report pending-capacity and stop
without evicting or suppressing queue work.
Resolve an optional local checkout with exactly one allowlisted helper call:
~/.agents/skills/pr-status/scripts/gh-pr-checkout.py OWNER/REPO HEAD_SHA \
--timeout REMAINING_SECONDS
This helper alone may enumerate registered workspace members and Git worktrees. Never improvise a
shell loop or run ls, find, git -C, git worktree, remote, or HEAD probes. When
checkout.available is true, pass its path to /review-pr; otherwise state remote-only evidence
and continue without local reads.
4. Invoke the immutable review
Require the still-premium route and invoke Skill(review-pr) non-interactively with the selected
qualified identity and exact queue head:
/review-pr owner/repo#123 --automation --premium-established \
--expected-head HEAD_SHA --deadline-seconds REMAINING_REVIEW_SECONDS \
[--checkout VERIFIED_HELPER_PATH]
Pass --checkout only from a checkout.available: true helper result. The review snapshot verifies
it again. Never create or update a checkout. Impose the normal runtime/turn bound in addition to the skill deadline.
Accept a report only when schema is review-pr/v1, status is complete, target repository/number,
node ID, and head match the queue item, the final revision check succeeded, and verdict is non-null.
Require the automation fields used below: changesOverview, evidence status/reasons and Jira
identity, unresolvedComments, acChecklist, concerns, and verdict. Never reconstruct missing
sections by rerunning analysis or guessing.
partial, stale, failed, malformed, timed-out, or interrupted runs remain unhandled and
retryable. Do not add a completed key or call --mark-reviewed. Show their errors and use warm
cadence; an explicit stale result enters the stale flow.
Persist the bounded in-flight record and increment reviewAttempts immediately before invoking
Skill(review-pr). Every premium invocation consumes one attempt, including a failed, stale,
timed-out, or interrupted run. If analysis is interrupted before a complete result, retry later
only when another attempt remains. Immediately after accepting a complete automation result,
persist it and set phase review-complete before rendering any report text. Retain that exact
result with each later phase transition. If the report
is already visibly present at the report-rendered checkpoint, do not run duplicate successful
analysis: reverify that result and continue the idempotent local-completion transaction. If visible
output continuity is uncertain, stop and ask for an explicit recheck rather than claiming completion.
5. Render the complete review report
Render the complete human-readable review before any disposition question, including:
- repository/PR, immutable head/base, request event/requester, and author;
- snapshot, exact-head CI, Jira, checkout, and evidence-completeness status;
- changes overview and unresolved reviewer comments;
- AC checklist, every concern with evidence, and verdict.
Set the phase to the report-rendered checkpoint only after all sections are visible. A summary or verdict alone is not a completed report.
6. Reverify after rendering
Before recording completion or offering approve/comment/request-changes, call:
~/.agents/skills/review-pr/scripts/gh-pr-snapshot.py 'owner/repo#123' \
--expected-head REVIEWED_HEAD --expected-base REVIEWED_BASE \
--expected-state-key REVIEWED_STATE_KEY --verify-only \
--timeout REMAINING_SECONDS
Anything except complete makes the rendered report stale. Do not mark it complete and do not
offer GitHub approval, comment, or request-changes submission. Enter the stale flow.
7. Mark locally reviewed
Treat local completion as an idempotent transaction keyed by workKey:
Before collector marking, create or replace the matching pending record in
verified-unmarkedstate with the complete result and set the in-flight phase likewise. If pending capacity is unavailable, do not mark.Run the local state reducer:
~/.agents/skills/pr-status/scripts/gh-pr-review-requests.py \ --state-json 'PRIOR_STATE_JSON' --mark-reviewed 'WORK_KEY'Retain its returned collector state, add
workKeytocompletedWorkKeys, change the pending record tofresh, and clear in-flight state. Adding the same key is idempotent. Do not changereviewAttemptshere; the attempt was charged before analysis.
This order makes interruption recovery safe. A verified-unmarked record is always reverified,
then the idempotent mark command may be repeated before completing the same keyed transaction. A
collector state that already names the same handledWorkKey is success, not another completion.
Never mark or add a completed key for a failed, partial, stale, malformed, or interrupted report.
The reducer changes only supplied session state; it does not write GitHub.
8. Ask for disposition
Ask for disposition only after local completion. Use AskUserQuestion with one single-select
question and these four choices:
- Keep private (Recommended) — resolve the pending disposition with no message or GitHub action.
- Prepare GitHub draft — proceed to the draft-type question below; this does not submit.
- Prepare Slack draft — render a paste-ready summary; this does not send.
- Defer — retain the pending disposition for a later tick without rerunning the review.
A custom answer may refine draft wording but is not permission to send. When the answer returns,
reverify the immutable head/base/state before applying any choice; a question may have remained
open while the PR changed. On a revision/state mismatch, ignore the selected action, change the
record to stale, and enter the stale flow. An unavailable or failed verifier also blocks the
choice but retains fresh state for bounded retry. This recheck is mandatory before exposing
GitHub review kinds.
A verified Keep private removes the pending record. A verified Defer changes it to deferred and
allows the next queued PR to proceed if time and budget remain.
GitHub draft
After that fresh verification, ask a second single-select question with Approve, Comment,
and Request changes. Generate concise text grounded only in the rendered review; show the exact
repository, PR, review kind, and body. Save the immutable target, selected kind, exact body, and later shown quoted-heredoc command
in github-draft state. Then ask:
- Keep draft (Recommended) — no external action.
- Verify for submission — run the immutable verifier again; this is not submission permission.
If verification succeeds, show the exact target/kind/body again and ask a final question with
Submit now and Keep draft (Recommended). Only Submit now authorizes submission. When the
Submit now answer returns, reverify the immutable head/base/state once more because the final
question may have remained open. On anything except complete, do not send and enter the stale or
bounded retry flow. On success, immediately run the following command without another tool call or
content change:
gh pr review PR_NUMBER --repo OWNER/REPO REVIEW_KIND_FLAG --body-file - <<'UNIQUE_REVIEW_BODY'
EXACT_SHOWN_BODY
UNIQUE_REVIEW_BODY
Map REVIEW_KIND_FLAG to exactly one of --approve, --comment, or --request-changes. Before the
final prompt, choose and show a quoted high-entropy heredoc delimiter absent from the body; include
the final newline in the shown draft. Quoting the delimiter disables shell interpolation, and
--body-file - preserves the shown body as one value. After post-answer verification, execute that
exact already-shown command without rebuilding it. No other answer may submit. Keep draft retains github-draft without automatic re-prompting. A successful submission
removes the pending record; a failed attempt retains it as github-draft. Stop the watcher after
either bounded submission result.
Slack draft
Render a paste-ready terse summary with PR URL, reviewed head, verdict, key evidence, and requested
next step, then retain it in slack-draft. Do not infer a destination. This watcher deliberately
has no Slack send tool or Skill permission and never sends. Default to Keep draft (Recommended)
and name the separate configured Slack workflow, if one exists, as the only handoff. That later
workflow must show the exact destination and text and obtain its own Send now confirmation
immediately before sending; this watcher supplies no send authorization.
9. Handle stale reports
Label the report stale with expected and observed state. Do not offer GitHub
approve/comment/request-changes choices. For a review that became stale before local completion,
do not call --mark-reviewed or add its key. For an already marked pending report that became
stale while its question was open, keep the old reviewed-head key as historical completion but
change the pending record to stale; the new head has a different work identity and is not
complete. Never attempt to unmark collector state.
Ask one single-select question:
- Rerun current head (Recommended) — retire the old pending record while preserving its
historical completed key, then, if deadline and budget allow, collect with
--recheck, select only this qualified PR, and repeat the serial flow against its new exact head. This removes the same-PR gate and keepsdisposition owner/repo#123unambiguous. If it is a draft, render that it still waits and do not invoke/review-pr. - Keep private — remove the stale pending record; the new head remains unreviewed.
- Defer — retain it in
stalestate without automatically prompting again.
A stale rerun consumes another premium attempt and adds a completion only for the new workKey,
after its complete report is rendered, verified, and marked. The old and new immutable-head
identities are never conflated.
10. Complete and pace
Never call a scheduler while an AskUserQuestion is open. The next-tick: line is terminal visible output. End visible tick output with exactly one:
next-tick: {hot|warm|cold} (~{N}s) — {reason}
- hot (~180s): actionable direct work remains and time/budget allow;
- warm (~600s): partial/failure retry, deferred disposition, stale rerun, or interrupted work;
- cold (1200 → 1500 → 1800s): complete no-change ticks, increasing the quiet streak.
Reset the quiet streak on hot/warm work. In adaptive Pi ticks, call action: complete with
outcome: continue and delaySeconds: N; fixed mode omits the delay. Use outcome: stop after the
deadline, review budget exhaustion after the current complete result and disposition finish,
third consecutive failure, external send attempt, or explicit stop/handoff. A healthy no-change
poll contains only the single human-facing status line specified above and this scheduler protocol
line; it never asks a question or repeats a completion summary. Never emit a line beginning
Tick complete, Queue empty, or Watcher continues; after next-tick: make the scheduler call
when required, otherwise end the turn without more prose.