ワンクリックで
ci-watch
Poll GitHub Actions CI for a PR, auto-fix failures, and report terminal state
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
メニュー
Poll GitHub Actions CI for a PR, auto-fix failures, and report terminal state
Codex または Claude でインストール この Prompt をコピーして Codex、Claude、または他のアシスタントに貼り付けると、Skill ページを確認してインストールできます。
SOC 職業分類に基づく
| name | ci-watch |
| description | Poll GitHub Actions CI for a PR, auto-fix failures, and report terminal state |
When invoked, your FIRST and ONLY action is to dispatch to a polymorphic-agent. Do NOT read files, run bash, or take any other action before dispatching.
Agent(
subagent_type="onex:polymorphic-agent",
description="Run ci-watch for PR #<pr_number>",
prompt="Run the ci-watch skill. <full context and args>"
)
CRITICAL: subagent_type MUST be "onex:polymorphic-agent" (with the onex: prefix).
Poll GitHub Actions CI status for a pull request. Auto-fix test/lint failures and re-push. Exit
when CI reaches a terminal state: passed, capped (fix cycles exhausted), timeout, or error.
Announce at start: "I'm using the ci-watch skill to monitor CI for PR #{pr_number}."
Implements: OMN-2523
/ci-watch 123 org/repo
/ci-watch 123 org/repo --timeout-minutes 30
/ci-watch 123 org/repo --max-fix-cycles 5
/ci-watch 123 org/repo --no-auto-fix
The watch strategy uses gh CLI for CI status polling. The skill bootstrapper resolves
the handler from the runtime: skill-bootstrapper metadata in this SKILL.md.
Use _bin/ci-status.sh for CI status extraction:
# Option 1: Blocking wait (polls internally every 30s)
${CLAUDE_PLUGIN_ROOT}/_bin/ci-status.sh \
--pr {pr_number} --repo {repo} --wait --timeout {timeout_seconds}
# Option 2: Snapshot (check once, return immediately)
${CLAUDE_PLUGIN_ROOT}/_bin/ci-status.sh --pr {pr_number} --repo {repo}
The script wraps gh pr checks and gh run view --log-failed into structured JSON:
{
"pr": 123,
"repo": "org/repo",
"status": "passing|failing|pending|timeout",
"checks": [...],
"failing_checks": [...],
"log_excerpt": "..."
}
If neither inbox-wait nor _bin/ci-status.sh is available, use gh run watch directly:
Get PR head branch and latest run ID:
BRANCH=$(gh pr view {pr_number} --repo {repo} --json headRefName -q '.headRefName')
RUN_ID=$(gh run list --branch "$BRANCH" --repo {repo} -L 1 --json databaseId -q '.[0].databaseId')
If no run found yet: wait 30s and retry (up to 5 attempts).
Block until run completes:
gh run watch "$RUN_ID" --repo {repo} --exit-status
EXIT_CODE=$?
If all checks passed: exit with status: passed
If failures detected and auto_fix=true:
TRIAGE each failing check — classify as pre-existing or introduced:
BASE_BRANCH=$(gh pr view {pr_number} --repo {repo} --json baseRefName -q '.baseRefName')
BASE_RUN_ID=$(gh run list --branch "$BASE_BRANCH" --repo {repo} -L 1 --json databaseId -q '.[0].databaseId')
BASE_FAILING=$(gh run view "$BASE_RUN_ID" --repo {repo} --json jobs \
-q '[.jobs[] | select(.conclusion == "failure") | .name]')
BASE_FAILING (failed on base branch before this PR)For each pre-existing failure:
Log: "Pre-existing CI failure: {check_name} — dispatching separate fix PR (not blocking current PR)"
Dispatch a background fix agent targeting main (not this branch):
Task(
subagent_type="onex:polymorphic-agent",
run_in_background=True,
description="ci-watch: fix pre-existing CI failure [{check_name}] in {repo}",
prompt="Pre-existing CI failure detected in {repo}.
Check name: {check_name}
Failure log:
{failure_log}
This failure exists on {base_branch} BEFORE PR #{pr_number}. The current PR did NOT
introduce it. Fix it on a fresh branch targeting {base_branch}.
Steps:
1. Create branch: fix/ci-preexisting-{sanitized_check_name}-{YYYYMMDD}
git -C {repo_path} fetch origin {base_branch}
git -C {repo_path} worktree add /tmp/preexisting-fix-{check_name} -b fix/ci-preexisting-...
2. Investigate and fix the root cause
3. Commit, push, and open a PR targeting {base_branch}
4. Report: branch, PR URL, root cause found, what was changed"
)
Increment preexisting_fixes_dispatched
Do NOT block the current PR — continue watching
If all failing checks are pre-existing (none introduced by this branch):
status: passed — the current PR is cleanFor each introduced failure (and cycles remaining):
gh run view --log-failedfix_cycles_usedIf introduced-failure fix cycles exhausted: exit with status: capped
If elapsed > timeout_minutes: exit with status: timeout
| Situation | Behavior |
|---|---|
| Base branch has no recent CI run | Treat failure as introduced (conservative) |
gh run list returns empty on base | Treat failure as introduced |
| Same check fails on both base and PR branch | Pre-existing — dispatch fix PR, don't block |
| Check does not exist on base (new check) | Introduced — fix on current branch |
| Base run is still in progress | Wait up to 2 minutes for it to complete, then proceed as introduced |
Task(
subagent_type="onex:polymorphic-agent",
description="ci-watch: auto-fix introduced CI failures on PR #{pr_number} (cycle {N})",
prompt="Invoke: Skill(skill=\"onex:ci-fix-pipeline\",
args=\"--pr {pr_number} --ticket-id {ticket_id}\")
Failure details:
{failure_log}
Fix the failure. Do NOT create a new PR — commit and push to the existing branch.
Branch: {branch_name}
Report: what was fixed, files changed, confidence level."
)
Output contract: ModelSkillResult from omnibase_core.models.skill
Note: This contract reference is behavioral guidance for the LLM executing this skill. Runtime validation not yet implemented.
Write to: ~/.claude/skill-results/{context_id}/ci-watch.json
| Field | Value |
|---|---|
skill_name | "ci-watch" |
status | One of the canonical string values: "success", "partial", "error" (see mapping below) |
extra_status | Domain-specific status string (see mapping below) |
run_id | Correlation ID |
repo | Repository slug (org/repo) |
pr_number | PR number |
extra | {"fix_cycles_used": int, "elapsed_minutes": int, "preexisting_fixes_dispatched": int} |
Note on
context_id: Prior schema versions includedcontext_idas a top-level field. This field is not part ofModelSkillResult— it belongs to the file path convention (~/.claude/skill-results/{context_id}/ci-watch.json). Consumers should derive context from the file path, not fromcontext_idin the result body.
Status mapping:
| Current status | Canonical status (string value) | extra_status |
|---|---|---|
passed | "success" (EnumSkillResultStatus.SUCCESS) | "passed" |
capped | "partial" (EnumSkillResultStatus.PARTIAL) | "capped" |
timeout | "error" (EnumSkillResultStatus.ERROR) | "timeout" |
error | "error" (EnumSkillResultStatus.ERROR) | null |
Behaviorally significant extra_status values:
"passed" → ticket-pipeline treats as SUCCESS; auto-merge continues unblocked"capped" → ticket-pipeline treats as PARTIAL; CI fix cycles exhausted but PR is not blocked — human may choose to merge with known CI debt"timeout" → ticket-pipeline treats as ERROR; CI watch timed out — retryablePromotion rule for extra fields: If a field appears in 3+ producer skills, open a ticket to evaluate promotion to a first-class field. If any orchestrator consumer (epic-team, ticket-pipeline) branches on extra["x"], that field MUST be promoted.
Example result:
{
"skill_name": "ci-watch",
"status": "success",
"extra_status": "passed",
"pr_number": 123,
"repo": "org/repo",
"run_id": "pipeline-1709856000-OMN-1234",
"extra": {
"fix_cycles_used": 1,
"preexisting_fixes_dispatched": 1,
"elapsed_minutes": 12
}
}
Note:
extra_status: "passed"means the current PR's branch is clean. Ifextra["preexisting_fixes_dispatched"] > 0, background fix PRs were opened for pre-existing failures found on the base branch. Those PRs are independent and do not block this result.
| Error | Behavior |
|---|---|
gh pr checks unavailable | Retry 3x, then status: failed with error |
| ci-fix-pipeline hard-fails | Log error, continue watching (don't retry fix) |
| Slack unavailable for gate | Skip gate, apply default behavior for risk level |
| Linear sub-ticket creation fails | Log warning, continue |
ci-watch.shBash wrapper for programmatic and CI invocation of this skill.
#!/usr/bin/env bash
set -euo pipefail
# ci-watch.sh — wrapper for the ci-watch skill
# Usage: ci-watch.sh --pr <PR> [--ticket-id <ID>] [--timeout-minutes <N>] [--max-fix-cycles <N>] [--no-auto-fix]
PR=""
TICKET_ID=""
TIMEOUT_MINUTES="60"
MAX_FIX_CYCLES="3"
AUTO_FIX_CI="true"
while [[ $# -gt 0 ]]; do
case "$1" in
--pr) PR="$2"; shift 2 ;;
--ticket-id) TICKET_ID="$2"; shift 2 ;;
--timeout-minutes) TIMEOUT_MINUTES="$2"; shift 2 ;;
--max-fix-cycles) MAX_FIX_CYCLES="$2"; shift 2 ;;
--no-auto-fix) AUTO_FIX_CI="false"; shift ;;
*) echo "Unknown argument: $1" >&2; exit 1 ;;
esac
done
if [[ -z "$PR" ]]; then
echo "Error: --pr is required" >&2
exit 1
fi
exec claude --skill onex:ci-watch \
--arg "pr_number=${PR}" \
--arg "ticket_id=${TICKET_ID}" \
--arg "policy.timeout_minutes=${TIMEOUT_MINUTES}" \
--arg "policy.max_fix_cycles=${MAX_FIX_CYCLES}" \
--arg "policy.auto_fix_ci=${AUTO_FIX_CI}"
| Invocation | Description |
|---|---|
/ci-watch --pr {N} | Interactive: poll CI on PR N, auto-fix on failure |
/ci-watch --pr {N} --no-auto-fix | Interactive: poll CI on PR N, gate on failure |
Skill(skill="onex:ci-watch", args="--pr {N} --ticket-id {T}") | Programmatic: composable invocation from orchestrator |
ci-watch.sh --pr {N} --ticket-id {T} --timeout-minutes 90 | Shell: direct invocation with all parameters |
ticket-pipeline invokes ci-watch from Phase 4 as a non-blocking background agent
(run_in_background=True). The pipeline does NOT block on or await the ci-watch result —
it advances to Phase 5 immediately after dispatching.
The background dispatch only occurs when the initial CI snapshot (taken in Phase 4) shows one or more failing checks. If CI is passing or pending, no dispatch happens at all and GitHub's auto-merge handles the rest.
Pass-through policy args forwarded from ticket-pipeline to ci-watch:
--max-fix-cycles {max_ci_fix_cycles} — max fix attempts before capping (default 3)--timeout-minutes {ci_watch_timeout_minutes} — max wait time (default 60); governs the
background ci-watch agent, not the ticket-pipeline itself# Example background dispatch from ticket-pipeline Phase 4:
Task(
subagent_type="onex:polymorphic-agent",
run_in_background=True,
description="ci-watch: fix CI failures for {ticket_id} PR #{pr_number}",
prompt="CI is failing for PR #{pr_number} in {repo} ({ticket_id}).
Invoke: Skill(skill=\"onex:ci-watch\",
args=\"{pr_number} {repo} --max-fix-cycles {max_ci_fix_cycles} --no-auto-fix\")
Fix any failures, push fixes. GitHub will auto-merge once CI is green."
)
ci-watch supports two notification modes, selected automatically based on infrastructure availability:
When Kafka and Valkey are available (ENABLE_REAL_TIME_EVENTS=true):
(repo, pr_number) via Valkey watch registrypr-status event arrives in the agent's inbox topic
(onex.evt.omniclaude.agent-inbox.{agent_id}.v1)from omniclaude.services.inbox_wait import register_watch, wait_for_pr_status
# Register watch for this PR
await register_watch(agent_id=agent_id, repo=repo, pr_number=pr_number)
# Wait for notification (replaces gh run watch polling)
result = wait_for_pr_status(
repo=repo,
pr_number=pr_number,
run_id=run_id,
agent_id=agent_id,
timeout_seconds=timeout_minutes * 60,
)
When Kafka/Valkey are unavailable:
gh run watch {run_id} --exit-status as background process~/.claude/pr-inbox/)OMNICLAUDE_MAX_WATCHERS=5)from omniclaude.services.inbox_wait import wait_for_pr_status
# Unified interface -- automatically falls back to STANDALONE
result = wait_for_pr_status(
repo=repo,
pr_number=pr_number,
run_id=run_id,
timeout_seconds=timeout_minutes * 60,
)
The original polling loop (gh run watch inline) is preserved as the STANDALONE fallback.
The wait_for_pr_status() function provides a unified interface that works in both modes.
No changes needed for existing callers -- the function handles mode detection internally.
CI status monitoring uses the skill bootstrapper for handler resolution. The runtime: skill-bootstrapper metadata in the SKILL.md front-matter directs the bootstrapper to
select the appropriate handler for CI polling operations.
| Mode | Backend | Latency | Details |
|---|---|---|---|
| Default | _bin/ci-status.sh | ~30s poll | Wraps gh pr checks + gh run view --log-failed |
| Fallback | gh run watch | ~30s poll | Direct GitHub CLI polling |
ticket-pipeline skill (Phase 4 dispatches ci-watch as a background agent on CI failure)pr-watch skill (runs after Phase 4 in ticket-pipeline)inbox_wait module (omniclaude.services.inbox_wait) — unified wait interfacenode_github_pr_watcher_effect — ONEX node for EVENT_BUS+ mode routing_bin/ci-status.sh — STANDALONE CI status extraction backendDispatch a background worker with role-templated prompt and auto-populated collision fences
Comprehensive system health monitoring — checks agent performance, database, Kafka topics, pattern discovery, and service status across the ONEX platform
Orchestrate a Claude Code agent team to autonomously work a Linear epic across multiple repos
Run DoD evidence checks against a ticket contract and generate a verification receipt
Autonomous per-ticket pipeline that chains ticket-work, local-review, PR creation, CI watching, PR review loop, integration verification gate, and auto-merge into a single unattended workflow with Slack notifications and policy guardrails
Full autonomous audit-debug-fix loop for all dashboard pages — Playwright recon, parallel systematic-debug, fix, PR, Linear ticket, re-audit, iterate until clean. Supports local and cloud targets with optional post-fix redeployment.