| name | ci-watch |
| description | Wait for a PR's GitHub CI checks to finish and return a slim pass/fail summary. Use this instead of manually polling `gh pr checks` over and over — it's ONE call that runs in the background and sleeps until every check settles, so the user can keep working; when it finishes it prints a one-line verdict per PR plus a compact per-check breakdown. Use when asked to "wait for CI", "watch the checks", "tell me when CI passes/fails", or after pushing a branch / opening a PR when the next step depends on CI being green. Watches multiple PRs in one call. |
| allowed-tools | Bash(bash:*), Bash(gh pr checks:*), Bash(gh pr view:*), Bash(gh run view:*), Bash(gh repo view:*), Bash(jq:*) |
ci-watch — watch a PR's CI in the background, return a slim verdict
Wait for GitHub Actions / status checks on one or more PRs to finish, then report
a tight pass/fail summary. The whole point is to replace the token-burning loop of
re-running gh pr checks by hand: launch a single background call and read one slim
result when it settles — the user keeps working the whole time.
How to use it
Always launch the script in the background so the user can keep working while CI
runs — invoke it with the Bash tool's run_in_background option set to true. The
script is one long-lived blocking loop that does all the waiting and sleeping
internally, so backgrounding it is exactly right: the harness keeps it alive across
turns and re-invokes you with a <task-notification> the moment it exits.
Run the script once. Do not wrap it in your own polling loop, do not call
gh pr checks yourself first, and do not run it in the foreground — a foreground
call blocks the whole conversation until CI settles, which is the very thing we're
avoiding. The script's loop returns only when every check has settled (or it times
out).
bash ~/.skills/.agents/skills/ci-watch/watch.sh
bash ~/.skills/.agents/skills/ci-watch/watch.sh 1234
bash ~/.skills/.agents/skills/ci-watch/watch.sh https://github.com/owner/name/pull/1234
bash ~/.skills/.agents/skills/ci-watch/watch.sh 1234 1240 --repo owner/name
Each of these is launched with run_in_background: true. When the skill ships linked
into a project, ~/.skills/... may instead be .claude/skills/ci-watch/watch.sh —
either path runs the same script.
After you launch it:
- Tell the user CI is being watched in the background and they can carry on —
you'll report back when it settles.
- The Bash tool returns an output file path right away. When the background task
exits you'll get a
<task-notification> carrying that same path — Read it to get
the slim verdict (the file captures the script's stdout and stderr).
- Relay the verdict. Only if a check failed do you need the
Full details: temp file
named on the last line of the output.
Arguments
PR ... — one or more PR numbers, URLs, or branch names. If omitted,
the current branch's PR is resolved via gh pr view.
--repo OWNER/NAME — repo for bare numbers/branches (default: the cwd's repo).
URLs carry their own repo.
Options & env vars
| Flag | Env var | Default | Meaning |
|---|
--interval DUR | CI_WATCH_INTERVAL | 30s | sleep between polls |
--timeout DUR | CI_WATCH_TIMEOUT | 30m | max total wait → exit 124 |
--grace DUR | CI_WATCH_GRACE | 90s | how long to wait for checks to appear before declaring "no checks" |
--fail-fast | CI_WATCH_FAIL_FAST=1 | off | return the moment any check fails |
DUR is plain seconds or a suffixed value: 45s, 30m, 1h.
Output (slim)
stdout carries only the summary; progress heartbeats go to stderr. Because the script
runs in the background, both streams are captured to the task's output file — Read
it after the completion notification to see this:
PR #1234 — CI FAIL (3/4, 4m12s)
✗ unit-tests fail 3m20s https://github.com/owner/name/actions/runs/…/job/…
✓ build pass 2m00s
✓ lint pass 41s
⊘ deploy skipping -
Full details: /tmp/ci-watch-1234-AbC123
- Verdict line per PR:
PR #<n> — CI <PASS|FAIL|TIMEOUT|NO CHECKS> (<passed>/<total>, <wallclock>).
passed counts skipped/neutral as passing; wallclock is how long the script waited.
- One line per check:
<symbol> <name> <bucket> <duration>. Symbols:
✓ pass · ⊘ skipping · ✗ fail · ⊗ cancel · · pending. The job URL is shown
only for failing checks.
- Never dumps logs to stdout. The final line is always
Full details: <path>.
The details temp file
The script writes a mktemp file (e.g. /tmp/ci-watch-<pr>-XXXXXX) containing the
full checks JSON for every PR and, for each failing Actions job, its
gh run view --job <id> --log-failed output. Its path is the last stdout line.
Read that file only if you need to dig into a failure — for a green run you can
ignore it.
Exit codes
| Code | Meaning |
|---|
0 | all checks passed (skipped/neutral count as pass; "no checks" is non-fatal) |
1 | at least one check failed or was cancelled |
124 | timed out before checks finished |
2 | setup/usage error (bad args, missing gh/jq, PR not found), or a watched PR became unreadable mid-run (ERROR verdict — e.g. PR deleted, auth/repo access lost) |
With multiple PRs the exit code is the worst outcome: any failure → 1, else any
error → 2, else any timeout → 124, else 0.
Behaviour notes (why this exists)
These are the hand-polling traps the script handles for you:
- State comes from the JSON
bucket, never the exit code. gh pr checks exits
non-zero while checks are pending or failing, so naive exit-code logic
misfires — the script ignores gh's exit status and decides from the parsed JSON.
- Freshly pushed PRs briefly report no checks. The script keeps waiting through
the
--grace window for checks to register instead of treating "no checks" as
"done". Only after grace with still-zero checks does it report NO CHECKS.
- Transient
gh/network errors are retried, not fatal — a failed poll just
retries on the next interval until --timeout. Permanent errors (PR/repo
not found, auth loss) are detected and reported immediately (ERROR) rather
than retried pointlessly until the timeout.
- It blocks until nothing is pending (queued / in_progress / pending), or
--fail-fast trips, or the timeout hits.