1. Shebang (Codex P2): scripts/agent-bootstrap.sh and .claude/hooks/session-start.sh now use #!/usr/bin/env bash instead of #!/bin/bash, matching repo convention enforced by checkers.sh. 2. Fail-closed bd pin (Codex P2): .claude/hooks/session-start.sh local path now exits 2 with a warning when pinned or running version cannot be parsed, instead of silently succeeding. An unparseable version is not a pass. 3. pr-reviews failure (Codex P2): scripts/pr-watch no longer uses '|| true' to swallow pr-reviews failures. If pr-reviews fails (fetch error, malformed response), pr-watch now exits 6 with a warning. Empty successful fetch (no reviews) is still exit 0. - Added exit 6 to usage text and skill documentation - Added test case 'pr-reviews failure exits 6' using malformed JSON fixture - All 57 pr-watch tests pass Co-authored-by: Ed Langley <fiddlerwoaroof@users.noreply.github.com>
5.1 KiB
name, description
| name | description |
|---|---|
| pr-watch | Wait for a GitHub PR's checks to finish before merging or reporting on it. Use whenever you need to know whether a PR is green. Do not hand-roll a polling loop over gh pr view. |
Waiting on a PR
"Watching a PR" means tracking both CI checks and GitHub comments/reviews. A PR is not ready to merge just because checks are green — review feedback must be read and answered first.
CI Checks
scripts/pr-watch <pr-number> [--expect <sha>] [--timeout <sec>] [--interval <sec>] [--reviews]
exit 0 every check COMPLETED, none failed, MERGEABLE with a state of
CLEAN, UNSTABLE or HAS_HOOKS, and reviews fetched (if --reviews)
exit 1 a check failed (under any mergeability, BLOCKED included), or the
PR is not mergeable (CONFLICTING, DIRTY, BEHIND, DRAFT, or a state
it does not know)
exit 2 the head moved during the wait, or is not --expect
exit 3 timed out with checks still running, mergeability still UNKNOWN,
or still BLOCKED with every registered check green
exit 4 usage or environment error
exit 5 the PR is MERGED or CLOSED, at the head the watch pinned; it ends on
the first poll that sees it closed, which may be mid-wait
exit 6 checks green/mergeable, but pr-reviews failed (fetch error, not "no reviews")
--reviews (default): after checks complete, print all reviews and comments
via scripts/pr-reviews. Use --no-reviews to skip this. The reviews are shown
but do not change the exit code — answering them is your responsibility.
Exit 0 now means mergeable, not only green. GitHub computes mergeability
lazily and reports UNKNOWN for a while after the base moves. It also reports
BLOCKED for the moment before a required check that needs: another job has
registered. The watch waits out both, so you do not need to re-check
mergeable by hand after it. Exit 3 is not a pass. It may be a required
check that never ran. Why each state counts the way it does:
context/code/tooling.md, "pr-watch and mergeability" (improvise-edpl).
Pass --expect <sha> whenever you know which commit you are asking about.
It is the difference between "this PR is green" and "the thing I pushed is
green", and a PR here was once merged at a head that predated its own fix.
Reviews and Comments
Reviews are a separate API surface from the comment thread. Codex/ChatGPT
reviews arrive as reviews, not comments — checking only the comment thread
misses them entirely. pr-watch --reviews (the default) shows all three:
| Surface | What it is |
|---|---|
[REVIEW] |
The review's own body (pulls/{n}/reviews) |
[INLINE] |
A comment on a line of the diff (pulls/{n}/comments) |
[COMMENT] |
A comment on the PR thread (issues/{n}/comments) |
To fetch reviews directly: ./scripts/pr-reviews <n> — do not pipe it. The
script's $? must be read directly; piping or chaining loses the exit code.
Redirect to a file if you need to process it.
Review Obligations
Before treating a PR as done or merging:
- Read all review feedback — reviews against an earlier head may still apply
- Answer substantive feedback with attribution — every reply must include the attribution line (see below)
- Re-check after each push that new reviews target the current head SHA
- Don't wait indefinitely on Copilot/Codex — reviews arrive as quota allows; an absent review and an unrequested one look identical. The adversarial review that gates a merge is dispatched by the orchestrator; external reviews are read in addition, not instead.
GitHub Attribution (Hard Rule)
Every GitHub write must include an attribution line. Posts go out under the repository owner's account — an unattributed post reads as the human's words.
Open the body with: _Posted by <Agent Stack> (<model>)_
Examples:
_Posted by Claude (Opus 4.5)__Posted by Cursor Agent (claude-sonnet-5)_
A thread is not "answered" until the reply carries this attribution.
See AGENTS.md "GitHub Attribution" for the full rule and "Shipping a wave" for Copilot request mechanics and how to answer declined findings.
Do not write the loop yourself
Four signals look like "finished" and are not:
- "nothing pending" — also true when no check has registered yet. Require a non-zero total as well.
conclusion == null—conclusionis a string, empty while running. Teststatus == "COMPLETED".- a verdict from before the head moved — re-check the head, or
--expect. mergeable: UNKNOWN— GitHub has not computed it yet. It is not a pass, and PRs #172 and #179 both went green on it.
gh is shell-aliased to a different tool in these shells; pr-watch locates a
real one itself. Do not reintroduce a bare gh call in a script here.
Before deciding a PR is stuck
Check suites can take several minutes to appear at all, and a Copilot review
takes about six. Compare date -u against the GitHub timeline rather than a
felt interval, and do not close and reopen a PR to unstick it.
scripts/pr-watch-test.sh drives every case from fixtures.