CI Loop (hns-workflow-ci-loop)
SkillDev toolsUnified CI watch + auto-fix loop skill. Polls gh pr checks after /moai sync PR creation, classifies required vs auxiliary failures, attempts safe automated patches (max 3 iterations), and escalates semantic failures to the user. Use for CI loop workflow — NOT for general loop iteration patterns (see moai-workflow-loop).
Available today. Use it from your connected AI after setup.
No other account needed.
Connect ahel once, and every AI you use reads what you have installed.
Then ask your AI: use the CI Loop (hns-workflow-ci-loop) skill
What this skill tells your AI
The instructions your AI receives, as published by modu-ai/moai-adk in .claude/skills/hns-workflow-ci-loop/SKILL.md and read by ahel’s review.
Unified CI watch + auto-fix loop. The orchestrator invokes this skill after /moai sync
Phase 4 (gh pr create) returns a PR number. The skill polls required checks, classifies
failures into mechanical vs semantic, attempts safe patches up to 3 iterations, and
escalates semantic failures via AskUserQuestion.
Quick Reference
Trigger: /moai sync Phase 4 PR-create returns a PR number, or an existing PR needs
CI monitoring.
Two phases, one skill:
- Watch — Poll
gh pr checksevery 30s, classify required vs auxiliary via.github/required-checks.ymlSSoT; exit on green/fail/timeout. - Auto-fix — On required-fail (exit 2), receive JSON handoff, run up to 3 patch iterations, escalate semantic failures immediately.
One-liner:
MOAI_CIWATCH_GH=gh sh scripts/ci-watch/run.sh <PR_NUMBER> <BRANCH>
Exit codes:
0— all required passed → ready-to-merge AskUserQuestion1— fatal error → surface remediation2— required failure → JSON handoff → auto-fix phase3— 30-min hard timeout → blocker message, return control
HARD invariants:
- AskUserQuestion is orchestrator-only — CLI, shell scripts, and the
manager-develop(cycle_type=autofix) subagent MUST NOT call it. - Force-push is absolutely prohibited (
--force,-f,--force-with-leaseall banned). - Max 3 auto-fix iterations; iteration 4+ triggers mandatory blocking AskUserQuestion.
- Semantic failures (race, deadlock, panic, assertion) are never auto-patched.
- Protected files:
.env*, credentials,.claude/settings*.json,.github/required-checks.yml,scripts/ci-watch/run.sh.
Implementation Guide
Phase 1 — Watch Loop
Polling cadence: 30 seconds minimum (GitHub API rate-limit). Override via
CIWATCH_POLL_INTERVAL env, never below 30 in production. Test mode uses
MOAI_CIWATCH_NO_SLEEP=1 (single-tick exit).
30-minute hard timeout: CIWATCH_TIMEOUT_SECONDS=1800 default. On timeout, exit 3.
Do not auto-restart.
Required vs auxiliary: Required checks live in .github/required-checks.yml
branches.<pattern>.contexts. Auxiliary checks listed under auxiliary: MUST NOT block
ready-to-merge. Hardcoding check names in scripts is prohibited.
State file: .moai/state/ci-watch-active.flag (YAML). Tracks pr_number, started_at,
heartbeat_at, required_checks, abort_requested. Heartbeat staleness > 90s allows
takeover. Abort: moai pr watch --abort.
Background watch standardization: For long-running PRs (5+ min), use
gh pr checks <PR> --watch invoked via run_in_background: true. Sleep + poll loops are
prohibited — they block the orchestrator's main session.
Status report format (stderr, state-change ticks only, no ANSI):
[ci-watch] PR #<N>: required 4/6 pass, 2 pending; advisory 0 fail
Handoff schema on exit 2 — JSON with stable fields: prNumber, branch,
failedChecks[] (each entry {name, runId, logUrl}), auxiliaryFailCount,
totalRequired. Field stability: name, runId, logUrl are stable contract — do not
rename. Schema source: the CI-watch handoff struct.
Phase 2 — Auto-Fix Loop
Entry condition: ci-watch exit 2 + valid JSON handoff. State file:
.moai/state/ci-autofix-<PR>.json (PR-scoped, 24-hour staleness threshold).
OQ2 cadence matrix (single source of truth for iteration behavior):
- iter 1, any mechanical sub_class → confirm + apply via AskUserQuestion (1st option = "Apply patch (Recommended)").
- iter 1, semantic/unknown → escalate (no patch attempt) via AskUserQuestion with diagnosis report.
- iter 2-3, mechanical + sub_class=trivial → silent apply + log (no AskUserQuestion).
- iter 2-3, mechanical + sub_class=non-trivial → confirm + apply via AskUserQuestion.
- iter 2-3, semantic/unknown → escalate (no patch) via AskUserQuestion.
- iter 4+ → mandatory blocking AskUserQuestion (no timer, options: manual fix / revise SPEC / abandon PR).
"trivial" = whitespace, gofmt/goimports, import-order (matches classify.sh RX_TRIVIAL_*).
Patch commit rule: Every patch = new commit. Format:
fix(ci): auto-fix <classification> failure (iter <N>). After push, re-invoke
scripts/ci-watch/run.sh to restart the watch loop.
Iteration 4+ escalation (mandatory blocking, no silent timeout):
- (Recommended) Manual fix — investigate and fix manually
- Revise SPEC — revise the SPEC and restart implementation
- Abandon PR — close the PR and abandon this approach
manager-develop (cycle_type=autofix) spawn prompt injects: handoff JSON, classification + sub_class,
failed CI log + PR diff, mode directive (mechanical → propose unified-diff patch;
semantic/unknown → return diagnosis only, no patch). HARD: no AskUserQuestion call from
the subagent — return Markdown only.
Audit log: .moai/logs/ci-autofix/<PR-NNN>-<YYYY-MM-DD>.md. Append-only. Each
iteration records classification, sub_class, action, patch_sha, escalation_reason.
Protected Files (never auto-modified)
**/.env,**/.env.***/credentials*,**/*_key.json,**/*secret*.claude/settings.json,.claude/settings.local.json.github/required-checks.yml(Wave 1 SSoT, read-only for Wave 2/3)scripts/ci-watch/run.sh(Wave 2 invariant)
If the manager-develop (cycle_type=autofix) subagent proposes a patch touching any of these, reject and escalate.
Go Helpers and Shell Scripts
Go helpers: the CI-watch classifier (required-vs-auxiliary), the handoff JSON-schema
struct, the watch state file, and the PR-watch report emitters (EmitReadyToMergeReport,
EmitFailureHandoff). Shell: scripts/ci-watch/run.sh (main
loop, mock via MOAI_CIWATCH_GH); scripts/ci-watch/lib/classify.sh (yq + grep
fallback); scripts/ci-autofix/log-fetch.sh (failure log + PR diff);
scripts/ci-autofix/classify.sh (mechanical vs semantic).
gh CLI compat: requires gh >= 2.50 for the workflow JSON field. On older gh,
classify.sh falls back to name-based heuristics from the required: list.
Works Well With
-
manager-develop(cycle_type=autofix) — failure diagnosis + patch proposal subagent -
manager-git— commit/push of auto-fix patches -
.claude/rules/local/ci-watch-protocol.md— HARD watch invocation contract (dev-only; governsscripts/ci-watch/) -
.claude/rules/local/ci-autofix-protocol.md— HARD autofix invocation contract (dev-only; governsscripts/ci-autofix/)Both are the dev-repo twins, not the distributed rules of the same basename. The distributed
.claude/rules/moai/workflow/ci-autofix-protocol.mdis deliberately script-free and governs themanager-developautofix cycle instead; it does not describe the scripts this skill runs.ci-watch-protocol.mdis not distributed at all. -
.github/required-checks.yml— Wave 1 SSoT
Common Rationalizations
- "Skip watch loop for small PRs" — small PRs fail CI too. Loop costs nothing, saves manual polling.
- "Auxiliary failures should block merge" — advisory by SSoT definition. Edit
required-checks.ymlto change classification. - "Try auto-patching even semantic failures" — semantic failures (race, assertion) cannot be auto-patched without context; wrong patch is worse.
- "Clean up history with force-push" — force-push destroys reviewer diff visibility. Always a new commit.
- "Time out after iter 3" — silent timeout prohibited; user must decide explicitly.
- "Apply trivial fixes without confirming" — iter 1 always confirms; iter 2+ trivial may silent apply.
Red Flags
manager-develop(cycle_type=autofix) subagent calls AskUserQuestion (HARD: orchestrator-only)- Iteration 4 auto-continues without blocking AskUser
git push --force/-f/--force-with-leaseanywhere in scripts- Semantic classification produces a patch attempt
- State file missing → iteration counter lost → infinite loop risk
- Watch polling interval < 30s (rate-limit risk)
required-checks.ymlmodified withoutmoai github initre-run
Verification
-
bash scripts/ci-watch/test/run_test.shpasses all shell tests -
go test ./internal/ciwatch/... ./internal/cli/pr/... -racepasses -
internal/ciwatch/coverage >= 85% - No ANSI codes in
FormatStatusUpdate()output -
EmitReadyToMergeReportfirst option carries(Recommended) - CLI does NOT call AskUserQuestion
-
grep -r 'push -f\|push --force' scripts/ci-autofix/ scripts/ci-watch/returns no matches - Audit log
.moai/logs/ci-autofix/<PR>-<DATE>.mdcontains every iteration
Signals
- GitHub stars
- 1k
- Forks
- 221
- Last commit
- Sep 2026
Advanced
- Catalog kind
- skill
- Gateway key
hns-workflow-ci-loop- Source
- github.com/modu-ai/moai-adk