Use when the user wants to monitor GitHub PR CI status, wait for checks to finish, merge on green, or investigate failed checks. Triggers on phrases like "is CI done", "wait for checks", "watch the PR", "merge when green", or after pushing commits that invoke CI workflows. Prevents wasteful polling of `gh pr checks` and encodes the blocking/background decision.
Instal·la
npx skillscat add igor1309/ai-labs/pr-ci-watch Instal·la des del registre de SkillsCat.
PR CI Watch
Core rule
Never poll. gh pr checks without --watch is a snapshot. Calling it in a loop — with or without sleep between calls — is forbidden. Estimating CI duration and sleeping that long before checking is also forbidden. One call, --watch, branch on exit code.
The command
Always redirect output to a file — raw --watch output contains hundreds of duplicate status lines across refreshes that bloat the context window. Branch on exit code, read the log only on failure.
gh pr checks <pr-number> --watch --fail-fast > /tmp/<repo>-pr-<pr>.log 2>&1- Exit 0: the watch reached a passing state. Nonzero: diagnose the signal below;
it may be a failed check, cancellation, missing registration, or CLI error.
Exit 8 means checks are still pending (it should not occur with--watch, but
handle it defensively). --fail-fastis the default for fast feedback. Drop it if the user wants to see all failures at once.- No PR number given? Resolve from current branch:
gh pr view --json number -q .number. - Set
timeout: 600000on the Bash tool call —--watchcan block indefinitely if a check hangs. - The
/tmpredirect may trigger a write permission prompt on first use. This is a one-time grant — worth it to avoid polluting context with hundreds of status lines on every CI watch.
Foreground by default
Run --watch in the foreground. The agent is cheap while blocked on a subprocess — no tokens burn, no context grows, resume is instant when checks settle.
Switch to background only if the user explicitly says they want to keep working in parallel — use run_in_background: true on the Bash tool call.
You will be notified when the background command completes. Read the log at /tmp/<repo>-pr-<pr>.log if details are needed.
On success
Report
Summarize the final state in 2–3 lines. Do NOT echo the raw --watch output — it contains hundreds of duplicate status lines across refreshes. Report:
- PR number and URL
- Overall status (all green)
- Notable outcomes: which jobs were skipped (validates path filters), which passed, total CI wall time if visible
Auto-merge
Check whether auto-merge is already enabled before acting:
gh pr view <pr> --json autoMergeRequest -q '.autoMergeRequest'- Auto-merge already enabled → skip to mergeability verification (below). Do not call
gh pr mergeagain. - Auto-merge not enabled and user asked to merge on green → enable it:
gh pr merge <pr> --merge --auto --delete-branch- User did not ask to merge → skip merge, just report CI status.
Verify mergeability
CI green does NOT mean the PR can merge. Auto-merge is blocked by conflicts, missing approvals, or other branch-protection rules. After checks pass, always verify:
gh pr view <pr> --json state,mergeable,mergeStateStatus,autoMergeRequest,reviewDecision,statusCheckRollup -q '.'state: "MERGED"→ the PR already merged (auto-merge fired while CI was settling). Report success and stop — no further checks needed.
Handle UNKNOWN state: GitHub often returns mergeable: "UNKNOWN" or mergeStateStatus: "UNKNOWN" immediately after CI settles — the merge check hasn't finished computing yet. Wait 60 seconds and retry, up to 2 retries. On each retry, check state first — the PR may have auto-merged in the meantime. After 2 retries still UNKNOWN, report it explicitly as unresolved — do NOT treat it as success.
Interpret the result:
mergeable: "MERGEABLE"+mergeStateStatus: "CLEAN"→ auto-merge will proceed, report success.mergeable: "CONFLICTING"→ auto-merge is blocked. Report the conflict immediately and offer to resolve it (fetch base branch, merge locally, fix conflicts, push). Do NOT tell the user "auto-merge will proceed" or "wait a moment."mergeStateStatus: "BLOCKED"withmergeable: "MERGEABLE"→ a branch-protection rule is unsatisfied. UsereviewDecisionandstatusCheckRollupfrom the same response to name the missing approval or required check.
Never declare a PR will auto-merge based solely on CI status. The mergeability check is mandatory.
Diagnose by signal
Do not collapse every non-green result into "CI failed." Classify the signal
before choosing the next command:
| Signal | Classification | Next action |
|---|---|---|
--watch exits nonzero and gh pr checks <pr> --json name,link,bucket,state contains bucket: "fail" or "cancel" |
Failed check | Pull only that run's failed steps with gh run view <run-id> --log-failed. |
--watch exits 0, but the mergeability query returns mergeStateStatus: "BLOCKED" |
Merge gate | Report the unsatisfied approval or required check from reviewDecision and statusCheckRollup. Do not hunt for a failed run unless a check bucket is also fail or cancel. |
The watch reports no checks reported on branch, the one allowed retry has elapsed, and gh pr checks <pr> --json name,link,bucket,state returns [] |
Check-registration gap | Inspect recent runs for the PR head branch with the command below. Report whether no workflow started, a workflow was skipped, or GitHub has not associated the run with the PR. |
An outer command exits 0 even though a producer visibly failed, especially after piping through tee |
Masked process failure | Re-run the producer without the pipeline, or run the pipeline under bash -o pipefail. Treat the original result as unknown, never green. |
For a check-registration gap:
head_branch=$(gh pr view <pr> --json headRefName -q .headRefName)
gh run list --branch "$head_branch" --limit 10 \
--json databaseId,workflowName,event,status,conclusion,urlFor a failed check, pull only what failed, not full logs:
gh run view <run-id> --log-failedGet run IDs from the --watch output or the check link returned bygh pr checks <pr> --json name,link,bucket,state. Summarize the failure in 1–3
lines, then ask before anything destructive (revert, force-push, rerun).
After a rerun, stream the retry instead of polling:
gh run rerun <run-id>
gh run watch <run-id> --exit-status > /tmp/<repo>-run-<run-id>.log 2>&1Run-level watching
gh pr checks --watch stays the default for "is the PR green?" — it covers all
checks and merge gating. gh run watch <run-id> operates one level down, on a
single workflow run: it follows that run to completion and, with--exit-status, exits nonzero if the run fails. Use it instead of the PR-level
watch when:
- the thing being watched is a specific workflow run, not a PR — e.g. a
tag-triggered release/deploy workflow ("watch the deploy", "watch run 12345"); - you just re-ran a failed check and want to follow the retry (above);
- only one run matters and the user wants its progress, not a PR status table.
Same discipline as the PR-level watch: redirect output to a file (watch
output repeats status lines across refreshes), branch on exit code, settimeout: 600000, foreground by default, background only to keep working in
parallel. Find run IDs withgh run list --workflow "<name>" --limit 1 --json databaseId,status — and if
the run was just triggered (e.g. by a tag push), wait ~20 seconds first or the
list comes back empty.
Known race: "no checks reported"
If --watch runs too soon after a push, it may exit 1 with "no checks reported on branch" before GitHub registers the workflow. Retry once after 5–10 seconds — still with --watch, never a poll loop.
Preserve exit status
Branch on the watch command's own exit status. Do not pipe a watch, test, build,
or deploy command through tee and trust the pipeline's default status: it may
report tee's success after the producer failed. The commands in this skill use
redirection for that reason. If a pipeline is unavoidable, preserve both quiet
capture and the producer's exit status:
bash -o pipefail -c 'producer 2>&1 | tee /tmp/<operation>.log >/dev/null'Forbidden
while ...; do gh pr checks; sleep N; done- Repeated
gh pr checksacross turns "to see if it's done yet" - Delegating the watch to a sub-agent (sub-agents are synchronous; solves nothing)
- Sleeping a guessed CI duration then checking