Agent Skillsnubjs/nub › ci-watch

ci-watch

GitHub

解决GitHub CLI原生监控CI的早期退出和瞬态错误问题。通过脚本轮询权威状态,确保CI任务真正结束且结果可信,支持PR合并或发布决策,适用于CI/CD流程中的自动化等待场景。

.claude/skills/ci-watch/SKILL.md nubjs/nub

Trigger Scenarios

需要等待GitHub Actions CI完成并获取最终结果 执行merge-on-green或release-on-green策略前验证CI状态 处理PR检查汇总以决定是否继续流水线

Install

npx skills add nubjs/nub --skill ci-watch -g -y
More Options

Non-standard path

npx skills add https://github.com/nubjs/nub/tree/main/.claude/skills/ci-watch -g -y

Use without installing

npx skills use nubjs/nub@ci-watch

指定 Agent (Claude Code)

npx skills add nubjs/nub --skill ci-watch -a claude-code -g -y

安装 repo 全部 skill

npx skills add nubjs/nub --all -g -y

预览 repo 内 skill

npx skills add nubjs/nub --list

SKILL.md

Frontmatter
{
    "name": "ci-watch",
    "metadata": {
        "internal": true
    },
    "description": "Watch GitHub Actions CI correctly with the gh CLI — block until a run \/ PR check rollup is TRULY terminal, then trust the exit code. Invoke (via the Skill tool) whenever you need to wait on CI after a push, tag, or PR-open and act on the result (merge-on-green, release-on-green, fail-fast on red). Encodes the premature-exit pitfall (raw `gh run watch` \/ `gh pr checks --watch` exit 0 while the run is still QUEUED with no jobs registered, and exit non-zero on a transient API blip) and the blessed fix: `scripts\/ci-watch.ts`, which waits for the target to EXIST, polls authoritative terminal status, fails fast on the first failing check, and exits with a status the orchestrator can trust. Run it as a detached run_in_background task."
}

Watching CI with the GitHub CLI

The pitfall: raw watchers exit early

gh run watch <id> --exit-status and gh pr checks <pr> --watch are not safe to arm right after a push / tag / PR-open:

  • Premature exit while QUEUED. With no jobs registered yet, gh sees "nothing in progress" and returns exit 0 even though the run is still queued.
  • Transient errors read as failure. A mid-watch HTTP 401: Bad credentials (token refresh) or a 5xx exits non-zero, indistinguishable from a real CI failure.
  • No native fix--interval only tunes the poll cadence.

Never trust a raw watcher's exit code alone. Re-verify terminal status with gh run view <id> --json status,conclusion (done only when status == "completed") or gh pr view <pr> --json statusCheckRollup (done only when every item is terminal). And always fail-fast — act on the first failing check, never wait for all checks to finish.

The blessed tool: scripts/ci-watch.ts

Blocks until the target is truly terminal, then exits with a trustworthy status. Dogfoods nub; runs under plain Node too.

nub  scripts/ci-watch.ts --run <run-id> [--repo o/r] [--timeout <min>]
node scripts/ci-watch.ts --pr  <number> [--repo o/r] [--timeout <min>]
  • --run <run-id> — watch a workflow run (polls gh run view --json status,conclusion,jobs).
  • --pr <number> — watch a PR's check rollup (polls gh pr view --json statusCheckRollup,…).
  • --repo <owner/repo> — defaults to the current repo.
  • --timeout <minutes> — wall-clock cap before giving up as pending (default 45).
  • --required <names> — comma-separated branch-protection check names to gate on (e.g. --required "CI gate"). Success fires the instant every required check is green; a ghost or a non-required check — pending or failed — never blocks, matching branch-protection semantics. Prefer it when you know the required check name.
  • --no-progress <minutes> — how long an unchanged incomplete set (all named checks green, only a ghost left) may sit before exiting 4 STUCK-but-safe (default 8).

What it fixes: waits for the target to EXIST (not-found / no-jobs-yet is "keep polling," never "done"); polls authoritative terminal state; fails fast on the first FAILURE/CANCELLED/TIMED_OUT/ STARTUP_FAILURE; never hangs on a ghost; tolerates transient gh/API errors (retried with exponential jittered backoff, 10s → cap 60s, 90s unauthenticated).

The ghost check — why a strict "all checks terminal" gate hangs

GitHub occasionally registers a check-run that never reports a status: PENDING, nameless, forever. A watcher waiting for every rollup item to be terminal blocks indefinitely even though every real check is green. So a nameless / never-terminating non-required check does not block a green verdict: once every named check is green and the incomplete set has been unchanged for --no-progress minutes, the watcher exits 4 (STUCK-but-safe) and the caller --admin merges. A named pending check is never treated as a ghost, so a real in-flight check is never green-lit early.

Exit-code contract

code meaning
0 completed AND all green
1 a check/job failed (the summary names which + the URL)
2 required/named checks still NOT green after --timeout (genuinely stuck)
3 usage / target-unresolvable / unrecoverable error
4 STUCK-but-safe — required/named checks all green, but a ghost check will never terminate; safe to --admin merge (the caller decides)

The final stdout line is a single self-describing summary, e.g. CI-WATCH run 27972328590: SUCCESS (25 job(s) green), CI-WATCH pr 73: FAILURE — check "Test (ubuntu-latest, node 22.13)" → FAILURE (https://…), or CI-WATCH pr 327: STUCK — required/named checks GREEN (51), 1 non-terminal ghost/non-required check(s): (unnamed); safe to --admin merge.

NEVER pipe the watcher — a pipe discards the verdict you gate on

The exit code IS the contract, and a pipeline throws it away: in ci-watch.ts … | tail -20, $? is tail's status, so a FAILED CI reads as SUCCESS. The pipe also buffers stdout until the pipeline ends, so the log stays empty while the watcher runs.

# WRONG — $? is tail's, a red CI looks green, and no interim output
node scripts/ci-watch.ts --pr 604 --repo nubjs/nub | tail -25; echo "exit=$?"

# RIGHT — redirect, then gate on the watcher's OWN status
node scripts/ci-watch.ts --pr 604 --repo nubjs/nub > /tmp/ci604.log 2>&1; rc=$?
tail -15 /tmp/ci604.log; echo "exit=$rc"

main() warns on stderr when it detects a piped stdout (fd 1 is a FIFO for | cmd, not for > file), but redirect by default rather than relying on the warning. Bash ${PIPESTATUS[0]} / zsh ${pipestatus[1]} recover the real status if a pipe is unavoidable.

Cross-check the rollup regardless of what the watcher says. Read gh pr view <pr> --json statusCheckRollup,mergeStateStatus directly and act on any FAILURE. A watcher that has produced nothing for a long stretch is a suspect, not a status.

For a merge-queue drain, prefer scripts/merge-cascade.ts (it gates positively and merges on green); reach for ci-watch.ts when you need to block on one run/PR and branch on the result.

Who runs the watcher

run_in_background behaves the same for the orchestrator and for a sub-agent: a backgrounded Bash command persists across turns and re-invokes its launcher on exit. Both patterns below are valid.

Merge-on-green (the default). The orchestrator runs the blocking watcher as its own run_in_background Bash task:

  1. Enqueue: append {"pr":N,"branch":"…","thread":"…","note":"…"} (optional "hold":true) to .fray/merge-queue.jsonl. Enqueue UNHELD only once the PR's FINAL head is pushed — a stale head can be green-but-wrong.
  2. Watch: the orchestrator runs node scripts/merge-cascade.ts --max-minutes 40 with run_in_background: true. It gates positively on the required CI gate (present + SUCCESS) + mergeable, merges --squash --admin, ff-pulls, dequeues, exits → re-invokes the orchestrator. It shares ci-watch's ghost carve-out (scripts/lib/ci-rollup.ts), so a still-running or failed REQUIRED gate always blocks and a red PR is never mis-merged.
  3. Landing agents PUSH-THEN-EXIT — they never watch; they report pushed <sha>, queued.

Self-contained landing agent (one agent traces push→merge): push the branch; launch node scripts/merge-cascade.ts --max-minutes 40 (or ci-watch.ts) for its OWN PR via run_in_background: true; end its turn; it is re-invoked when the command exits, reports merged/red, and iterates. Do not preempt a landing agent's background watch by checking CI yourself and merging manually mid-trace — that impatience is what breaks the flow.

Foreground chunk loop (fallback only) — for an agent that must actively iterate and cannot rest:

# Bash tool: foreground (NOT run_in_background), timeout: 570000  (9.5 min, under the 600000 cap)
nub scripts/ci-watch.ts --pr <N> --chunk          # --chunk caps the watch ~9 min and exits 2 with "RERUN to continue" if still pending
#   exit 0 = green → act    exit 1 = red → fix + re-push    exit 2 = pending → RE-RUN the SAME command    exit 3 = error

While it exits 2, call it again — each chunk completes within the cap (no kill, no orphan). Blocking in the sub-agent's foreground is fine: it is backgrounded relative to the orchestrator. A dispatch prompt for a self-gating landing agent must spell this loop out — a sub-agent won't infer it.

A CronCreate heartbeat (every ~4 min, one non-blocking gh pr view poll per queued PR) is a FALLBACK only if a background shell ever proves unreliable.

Version History

  • 46280a5 Current 2026-08-03 01:42
  • f966b97 2026-07-31 00:05

    修复了当stdout被管道传输时导致退出码丢失的问题;现在会检测并警告此类错误用法,推荐使用重定向捕获输出。

  • 4fd083f 2026-07-19 12:03
  • ab079b4 2026-07-11 18:56

    修复因无名或卡住的幽灵检查导致无限等待的问题;将成功判定改为基于必需检查集,非必需检查不再阻塞,防止合并队列永久挂起。

  • 44d0dd0 2026-07-05 11:02

Same Skill Collection

.agents/skills/linux-vm-test/SKILL.md
.agents/skills/windows-vm-test/SKILL.md
.claude/skills/audit-thread/SKILL.md
.claude/skills/download-stats/SKILL.md
.claude/skills/git-archaeology/SKILL.md
.claude/skills/md-toc/SKILL.md
.claude/skills/plan-thread/SKILL.md
.claude/skills/pm-perf-tracing/SKILL.md
.claude/skills/soak/SKILL.md
.claude/skills/todo/SKILL.md
skills/nub/SKILL.md
.agents/skills/agent-browser/SKILL.md
.claude/skills/ad-hoc-test/SKILL.md
.claude/skills/address-issue/SKILL.md
.claude/skills/aube-bump/SKILL.md
.claude/skills/aube-sync/SKILL.md
.claude/skills/ci-adhoc-test/SKILL.md
.claude/skills/cpu-reduction/SKILL.md
.claude/skills/dev-loop/SKILL.md
.claude/skills/impact-analysis/SKILL.md
.claude/skills/implementation-thread/SKILL.md
.claude/skills/prose-writing/SKILL.md
.claude/skills/release/SKILL.md
.claude/skills/remote-build/SKILL.md
.claude/skills/rust-build-hygiene/SKILL.md
.claude/skills/rust-build/SKILL.md
.claude/skills/visual-review/SKILL.md
.claude/skills/worktree/SKILL.md

Metadata

Files
0
Version
46280a5
Hash
1c6422e9
Indexed
2026-07-05 11:02

- 위키
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-08-03 19:51
浙ICP备14020137号-1 $방문자$