Loop template
CI Repair Loop
Detect a failing CI signal, inspect the failure details, make the smallest justified patch, rerun verification, and open or update a pull request without masking the real issue.
Human guide
Raw README.mdCI Repair Loop
The ci-repair loop turns a failing CI signal into a disciplined repair attempt: collect evidence, identify root cause, make the smallest justified patch, rerun verification, and update a pull request. It is intentionally slower than a blind "make it green" bot because the central risk is fixing symptoms or masking a real issue.
This directory is a draft template. "Draft" is registry/site operational metadata for the template directory, not a hidden loop.yaml field; the manifest itself uses only the fields defined by spec/loop-spec-v0.1.md.
Files
loop.yaml— agent-readable manifest for the CI repair loop.AGENT-INSTALL.md— exact install and operation guide for cron, GitHub Actions dispatch, and agent-kanban backends.verification/dry-read-ambiguity-checklist.md— dry-read checklist for this revision.verification/safety-gate-examples.md— examples of patches that do and do not pass the mandatory human gate.
The seven human questions
1. What loop do I need?
Use this loop when a repository needs a repeatable response to failing CI checks on pull requests, protected branches, or scheduled validation runs. It matches the CI repair pattern in docs/research/loop-landscape.md §2: detect failed checks, inspect logs, patch, rerun, and verify with green CI plus diff review.
Do not use this loop for product changes that merely happen to have tests. The trigger should be a failing CI signal, and the first output should be failure analysis, not a feature proposal.
2. What can it safely automate?
The manifest permits reading CI status and logs, reading repository files, running local tests, editing a repair branch, pushing that branch, opening or updating a pull request, and creating follow-up tasks. It forbids disabling required checks, weakening tests without evidence, deleting failing tests to get green CI, blanket retries/ignores that hide errors, leaking credentials, and merging non-test code without human diff review.
The most important safety rule maps to human_gates.required_for: any non-test production code patch must pause for maintainer or code-owner diff review before merge, even if CI is green.
3. What do I need to connect?
Minimum setup needs:
- a GitHub repository;
- CI checks that expose check-run or workflow-run logs;
- a token with
actions:read,checks:read,repo:contents:write, andpull_request:writescopes, stored in the backend secret store; - the repository's normal local verification commands (
npm test,pytest,cargo test,uv run ..., or similar); - a place to record repair state and log evidence, declared in
state.paths.
For agent-kanban, connect a board or scheduler that can start a repair task with the failing check URL, branch, and SHA. For GitHub Actions dispatch, install a workflow that creates an issue or task with the failed run evidence. For cron, configure the branch or PR query it should poll.
4. What can go wrong?
The loop can make CI green while hiding the actual bug. Examples: deleting an assertion, adding broad retries around a race, changing production code without understanding why a test failed, or marking a required check as optional. The template mitigates this by requiring failure analysis before a patch, tying every patch to log evidence, limiting retries to three repair attempts, and forcing human diff review for non-test code changes.
Other risks include stale logs, missing local reproduction, token leakage in comments, and confusing pre-existing main-branch failures with failures caused by the current pull request. The loop records evidence and creates a follow-up card for unrelated or pre-existing failures rather than hiding them.
5. How do I know it worked?
A completed run has four pieces of evidence:
- failure analysis naming the failed check, failing command, relevant log excerpt, branch, and SHA;
- a minimal pull request diff tied to that root cause;
- local verification output when local reproduction is possible;
- CI rerun evidence after the repair branch is pushed.
If the diff touches non-test production code, green CI is not enough. The result is not merge-ready until the human diff-review gate records approval.
6. How do I stop or roll it back?
For cron, remove the polling entry from crontab. For GitHub Actions dispatch, disable or delete .github/workflows/ci-repair-dispatch.yml. For agent-kanban, pause or remove the recurring/triggered task. To roll back a bad attempt, reset or revert the repair branch to the last CI-observed commit and keep the failure analysis artifact so a human can see what was tried.
The rollback policy intentionally preserves audit evidence: do not delete logs or PR comments just because a repair attempt was wrong.
7. What should I improve after the first run?
After the first few repairs, update the template's project-specific prompt with the repository's fastest reliable local verification commands, known CI-log locations, code-owner review rules, and common pre-existing failures. Review .loopmaster/ci-repair/logs/ monthly and prune rules that encouraged noisy or low-confidence patches.
Backend behavior
All backends share the same safety model and manifest validation command. Backend differences are only how the failing signal reaches the repair loop:
cronpolls a configured repository query and creates a repair task or issue when it finds failed checks. It does not merge repairs.github_actionsdispatches fromworkflow_runorworkflow_dispatch, collects failed run metadata, and opens a repair issue/task. The patch still goes through a normal PR.agent_kanbanstarts an agent task directly with the failed check URL, branch, SHA, and acceptance criteria. This is the best fit for full repair because the agent can inspect logs, edit a branch, run tests, and update the PR.
Prose-to-manifest map
- Trigger: README says failing CI signal;
loop.yaml.trigger.typeisci_eventwithevent: check_suite_failure. - Safe automation: README lists allowed and forbidden actions;
permissions.allowed_actionsandpermissions.forbidden_actionscarry the same boundaries. - Human gate: README requires review for non-test production code;
human_gates.required_forincludesmerging any non-test production code patchand related masking risks. - Verification: README requires analysis, diff, local verification, CI rerun, and review evidence;
verification.requirednames each requirement. - Rollback: README says revert/reset the repair branch and retain audit evidence;
failure_policy.rollbacksays the same.
The job description (loop.yaml)
View raw loop.yamlspec_version: "0.1"
id: ci-repair
name: CI Repair Loop
purpose: Detect a failing CI signal, inspect the failure evidence, make the smallest justified patch, rerun verification, and open or update a pull request without masking the real issue.
audience: [human, agent, operator, team]
trigger:
type: ci_event
event: check_suite_failure
default: failing pull request or protected-branch check
inputs:
sources:
- type: github_repo
name: Repository under repair
url: https://github.com/OWNER/REPO
- type: api
name: CI check runs and workflow logs
url: https://api.github.com/repos/OWNER/REPO/actions
- type: file
name: Project test and build configuration
path: .github/workflows/
environment_variables:
- GITHUB_TOKEN
files:
- .github/workflows/
- package.json
- pyproject.toml
- README.md
capabilities:
- ci_log_inspection
- root_cause_analysis
- minimal_patch_generation
- local_reproduction
- test_execution
- pull_request_update
- human_diff_review
permissions:
allowed_actions:
- read_ci_status
- read_ci_logs
- read_repository_files
- run_local_tests
- modify_test_code
- propose_non_test_code_patch
- push_repair_branch
- open_pull_request
- create_follow_up_task
forbidden_actions:
- disable_required_checks_without_approval
- weaken_assertions_without_root_cause_evidence
- delete_failing_tests_to_make_ci_green
- mask_errors_with_blanket_retries_or_ignores
- merge_non_test_code_without_human_diff_review
- expose_credentials_in_logs_or_comments
credential_scopes:
- repo:contents:write
- pull_request:write
- actions:read
- checks:read
- kanban:create
state:
paths:
- .loopmaster/state/ci-repair.json
- .loopmaster/ci-repair/logs/
retention: 30 days for logs; keep final PR evidence with the pull request
outputs:
artifacts:
- .loopmaster/ci-repair/YYYY-MM-DD-failure-analysis.md
- pull_request_diff
- ci_rerun_evidence
notifications:
- root_cause_summary
- safety_gate_required
- repair_result
tasks:
- follow_up_card_for_pre_existing_or_unrelated_ci_failure
verification:
required:
- failure analysis names the failing check, failing command, and exact log evidence
- patch is tied to a root cause and does not merely hide the failing signal
- relevant local test or build command is run before pushing when the environment supports it
- CI rerun evidence is recorded after the repair branch is pushed
- non-test code changes cannot merge until a human diff-review gate approves the patch
- if CI failure is pre-existing or unrelated to the change, the loop records evidence and creates a separate follow-up task instead of hiding the failure
commands:
- name: validate loop manifest
run: uv run --with check-jsonschema --with pyyaml check-jsonschema --schemafile spec/loop.schema.json templates/ci-repair/loop.yaml
expected: exits 0 and prints ok -- validation done
- name: validate all manifests and sibling docs
run: uv run --with pyyaml --with jsonschema python tools/validate_manifests.py
expected: exits 0 and reports Validation passed
- name: dry-read ambiguity checklist
run: 'test -f templates/ci-repair/verification/dry-read-ambiguity-checklist.md && grep -q "Unresolved ambiguities: 0" templates/ci-repair/verification/dry-read-ambiguity-checklist.md'
expected: exits 0 when a reviewer can install the loop without unresolved ambiguity
human_gates:
required_for:
- merging any non-test production code patch
- disabling or weakening required checks
- deleting or weakening tests or assertions
- adding blanket retries, ignores, sleeps, quarantines, or allow-failure settings
- declaring a failure pre-existing while the current diff touches the failing subsystem
approver: repository maintainer or delegated code owner
failure_policy:
retry: 3
escalate_after: third_failed_repair_attempt_or_safety_gate_rejection
rollback: revert the repair branch to the last CI-observed commit; keep failure analysis and comments for audit
maintenance:
freshness_cadence: monthly
owner: Loopmaster maintainers
audit_log: .loopmaster/ci-repair/logs/
backends:
cron:
schedule: "*/30 * * * *"
install_notes:
- Polls configured branches or pull requests for failed checks and creates repair tasks; it does not merge repairs by itself.
- Reads GITHUB_TOKEN from a persistent backend secret store or env file; never from an interactive one-time export.
github_actions:
workflow: .github/workflows/ci-repair-dispatch.yml
install_notes:
- Dispatches on workflow_run failure or manual workflow_dispatch and opens a repair issue or kanban task with failure evidence.
- The repair patch still goes through a normal pull request and the non-test-code human diff-review gate.
agent_kanban:
task_template: Repair the failing CI check by reading logs, proving root cause, making the smallest safe patch, rerunning verification, and blocking for human diff review before any non-test code merge.
install_notes:
- Best fit for autonomous repair because the agent can inspect logs, edit a branch, run tests, and open a PR while preserving gate evidence.
Install guide (for your agent)
Raw for agentsAgent install guide — CI Repair Loop
Run commands from the repository root unless a step says otherwise. Replace OWNER/REPO only with the target repository slug. Do not print token values; keep credentials in the backend secret store.
0. Preflight
git status --short
git remote get-url origin
python3 --version
uv run --with check-jsonschema --with pyyaml check-jsonschema \
--schemafile spec/loop.schema.json \
templates/ci-repair/loop.yaml
uv run --with pyyaml --with jsonschema python tools/validate_manifests.pyExpected output:
<clean or intentionally scoped git status>
<GitHub origin URL>
Python 3.x.x
ok -- validation done
Validation passed.Failure branches:
- schema validation failure — fix
templates/ci-repair/loop.yamlbefore installing any backend. - missing
tools/validate_manifests.py— merge the manifest-validator prerequisite first. - dirty worktree with unrelated changes — stop and isolate the repair install branch before continuing.
1. Required operating rule
Before installing any backend, copy this rule into the repository's CI-repair prompt, issue template, or runbook:
A CI repair may not merge non-test production code until a maintainer or delegated code owner reviews the diff and explicitly approves that the patch fixes root cause rather than hiding the failing signal. Green CI alone is insufficient for non-test code changes.This is not optional. It implements human_gates.required_for in loop.yaml and addresses the CI-repair risk from docs/research/loop-landscape.md §2: "Fixes symptoms; masks real issue."
2. Manual smoke test with a real failing run
Find a recent failed run without exposing credentials:
OWNER_REPO=$(git remote get-url origin | sed -E 's|.*github.com[:/]||; s|\.git$||')
OWNER=$(printf '%s' "$OWNER_REPO" | cut -d/ -f1)
REPO=$(printf '%s' "$OWNER_REPO" | cut -d/ -f2)
TOKEN_FILE="${HERMES_HOME:-$HOME/.hermes}/.env"
if [ -z "${GITHUB_TOKEN:-}" ] && [ -f "$TOKEN_FILE" ]; then
GITHUB_TOKEN=$(grep '^GITHUB_TOKEN=' "$TOKEN_FILE" | head -1 | cut -d= -f2- | tr -d '\r\n')
fi
test -n "${GITHUB_TOKEN:-}"
curl -fsS \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$OWNER/$REPO/actions/runs?status=failure&per_page=1" \
> /tmp/loopmaster-ci-repair-failed-runs.json
python3 - <<'PY'
import json
from pathlib import Path
runs = json.loads(Path('/tmp/loopmaster-ci-repair-failed-runs.json').read_text()).get('workflow_runs', [])
if not runs:
print('NO_FAILED_RUNS_FOUND')
else:
run = runs[0]
print(f"RUN_ID={run['id']}")
print(f"HEAD_BRANCH={run.get('head_branch')}")
print(f"HEAD_SHA={run.get('head_sha')}")
print(f"HTML_URL={run.get('html_url')}")
PYExpected output: either NO_FAILED_RUNS_FOUND or a failed run id, branch, SHA, and URL. If no failed runs exist, continue with install verification using the manifest checks; do not fabricate a failure.
Failure branches:
test -nfails — store a GitHub token in the backend secret store before using API-based backends.- curl returns 401/403 — token lacks
actions:reador repository access. - curl returns an empty run list — the repository currently has no failed runs; this is acceptable for install verification.
3. Backend A — cron polling
Cron is useful when no webhook is available. It polls failed runs and writes a small local evidence file; it does not patch or merge by itself.
Create a persistent env file:
mkdir -p .loopmaster/state .loopmaster/env .loopmaster/ci-repair/logs
cat > .loopmaster/env/ci-repair.env <<'EOF'
# Required: set in this untracked file or your process manager secret store.
# GITHUB_TOKEN=...
CI_REPAIR_OWNER_REPO=OWNER/REPO
CI_REPAIR_QUERY=status=failure&per_page=5
EOF
chmod 600 .loopmaster/env/ci-repair.env
printf '\n.loopmaster/env/*.env\n.loopmaster/ci-repair/logs/\n' >> .git/info/excludeEdit .loopmaster/env/ci-repair.env locally so CI_REPAIR_OWNER_REPO matches the target repo and GITHUB_TOKEN is present. Do not commit the env file.
Install the polling entry:
REPO_ROOT=$(pwd)
CRON_LINE="*/30 * * * * cd $REPO_ROOT && set -a && . ./.loopmaster/env/ci-repair.env && set +a && curl -fsS -H \"Authorization: token \\$GITHUB_TOKEN\" -H \"Accept: application/vnd.github+json\" \"https://api.github.com/repos/\\$CI_REPAIR_OWNER_REPO/actions/runs?\\$CI_REPAIR_QUERY\" -o .loopmaster/ci-repair/logs/latest-failed-runs.json >> .loopmaster/ci-repair/logs/cron.log 2>&1"
( crontab -l 2>/dev/null | grep -v 'latest-failed-runs.json' ; printf '%s\n' "$CRON_LINE" ) | crontab -
crontab -l | grep 'latest-failed-runs.json'Expected output: one crontab line containing latest-failed-runs.json.
Test the exact command outside cron:
set -a && . ./.loopmaster/env/ci-repair.env && set +a
curl -fsS \
-H "Authorization: token $GITHUB_TOKEN" \
-H "Accept: application/vnd.github+json" \
"https://api.github.com/repos/$CI_REPAIR_OWNER_REPO/actions/runs?$CI_REPAIR_QUERY" \
-o .loopmaster/ci-repair/logs/latest-failed-runs.json
python3 -m json.tool .loopmaster/ci-repair/logs/latest-failed-runs.json >/tmp/ci-repair-runs.pretty.jsonFailure branches:
crontab: command not found— cron backend is unavailable; use GitHub Actions or agent-kanban.- curl fails in cron but not in the shell — check
.loopmaster/env/ci-repair.envpermissions and theset -a && . ...segment. - token is missing — add it to the untracked env file or process manager secret store; do not rely on a one-time interactive
export.
Stop cron:
crontab -l | grep -v 'latest-failed-runs.json' | crontab -4. Backend B — GitHub Actions dispatch
This backend opens a repair issue when another workflow fails. It gathers evidence but does not auto-merge a patch.
Install the dispatch workflow:
mkdir -p .github/workflows
cat > .github/workflows/ci-repair-dispatch.yml <<'EOF'
name: CI repair dispatch
on:
workflow_run:
workflows: ["Manifest and content validation", "Build Astro site"]
types: [completed]
workflow_dispatch:
inputs:
run_url:
description: Failed run URL to repair
required: false
permissions:
actions: read
contents: read
issues: write
jobs:
dispatch:
if: ${{ github.event_name == 'workflow_dispatch' || github.event.workflow_run.conclusion == 'failure' }}
runs-on: ubuntu-latest
steps:
- name: Create repair issue
uses: actions/github-script@v7
with:
script: |
const run = context.payload.workflow_run || {};
const runUrl = core.getInput('run_url') || run.html_url || `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions`;
const headBranch = run.head_branch || context.ref.replace('refs/heads/', '');
const headSha = run.head_sha || context.sha;
const title = `ci-repair: ${run.name || 'manual failed run'} on ${headBranch}`;
const body = [
'A CI repair loop should inspect this failure before patching.',
'',
`- Failed run: ${runUrl}`,
`- Branch: ${headBranch}`,
`- SHA: ${headSha}`,
'',
'Required safety gate: non-test production code changes must receive human diff review before merge.',
'Do not disable checks, delete tests, weaken assertions, or add blanket retries without root-cause evidence.'
].join('\n');
await github.rest.issues.create({
owner: context.repo.owner,
repo: context.repo.repo,
title,
body,
labels: ['ci-repair']
});
EOF
git add .github/workflows/ci-repair-dispatch.yml
git diff --cached -- .github/workflows/ci-repair-dispatch.ymlExpected diff includes:
permissions:
actions: read
contents: read
issues: writeAfter the workflow is committed and merged, verify manually:
gh workflow run ci-repair-dispatch.yml -f run_url=https://github.com/OWNER/REPO/actions/runs/EXAMPLE
sleep 10
gh run list --workflow ci-repair-dispatch.yml --limit 1Expected output: a workflow run appears. If it succeeds, the repository has a new issue labeled ci-repair with the run URL and safety gate text.
Failure branches:
ghunavailable or unauthenticated — run the workflow from the GitHub web UI.- issue creation fails — confirm repository workflow permissions allow issue writes.
- workflow dispatch creates issues for noisy workflows — narrow the
workflows: [...]list to required checks only.
5. Backend C — agent-kanban repair task
Agent-kanban is the recommended full repair backend. The task prompt must include the failed run URL, branch, SHA, the exact safety gate, and expected evidence.
Self-contained task prompt:
Repair the CI failure for <OWNER/REPO>.
Inputs:
- Failed run URL: <URL>
- Branch: <BRANCH>
- SHA: <SHA>
- Failing check name if known: <CHECK>
Required steps:
1. Fetch the branch and inspect CI logs. Record failing command and exact evidence.
2. Reproduce locally when the environment supports the failing command.
3. Make the smallest patch tied to root cause. Do not delete tests, weaken assertions, disable checks, or add blanket retries/ignores unless a human explicitly approved that direction.
4. If the patch touches non-test production code, open/update the PR and block for human diff review before merge. Green CI alone is insufficient.
5. Push the branch, rerun or wait for CI, and record check-run evidence.
6. If the failure is pre-existing on main or unrelated to this diff, record proof and create a separate follow-up task instead of hiding it.
Completion evidence: failure analysis path/comment, changed files, local commands and exit codes, PR URL, CI check results, and human review status when non-test code changed.Using Hermes native cron scheduling from an agent session:
cronjob(action="create", name="CI repair poller", schedule="every 30m", workdir="/absolute/path/to/repo", prompt="Check for failed required CI checks. If a failed check exists, create or work a kanban CI repair task using the self-contained prompt above. Never merge non-test code without human diff review.")Expected result: the scheduler returns a job id. Each repair task must complete with failure analysis, PR evidence, CI evidence, and review-gate status.
Failure branches:
- no kanban or scheduler backend exists — use cron polling to create evidence files or GitHub Actions to create repair issues.
- agent patches without reading logs — reject the run; the loop requires root-cause evidence before a patch.
- agent claims green CI without a check-run URL or status query — reject the run as unverifiable.
- non-test code changed without review — keep the PR open and block for maintainer/code-owner approval.
6. Final install verification checklist
Before calling an install complete, record these commands and results:
uv run --with check-jsonschema --with pyyaml check-jsonschema \
--schemafile spec/loop.schema.json \
templates/ci-repair/loop.yaml
uv run --with pyyaml --with jsonschema python tools/validate_manifests.py
test -f templates/ci-repair/verification/dry-read-ambiguity-checklist.md
grep -q "Unresolved ambiguities: 0" templates/ci-repair/verification/dry-read-ambiguity-checklist.md
grep -q "human diff review" templates/ci-repair/verification/safety-gate-examples.mdExpected success:
ok -- validation done
Validation passed.If any expected line is missing, the install is not verified.
How we know it works
- Status
- tested
- Date
- 2026-07-07
- Stale after
- 2026-08-06
dated checks passed
templates/ci-repair/verification/dry-read-ambiguity-checklist.md
- templates/ci-repair/verification/README.md
- templates/ci-repair/verification/dry-read-ambiguity-checklist.md
- templates/ci-repair/verification/example-agent-task.md
- templates/ci-repair/verification/example-cron-output.json
- templates/ci-repair/verification/example-failure-analysis.md
- templates/ci-repair/verification/example-github-issue.md
- templates/ci-repair/verification/manifest-prose-map.md
- templates/ci-repair/verification/pr-evidence-template.md
- templates/ci-repair/verification/safety-gate-examples.md
- templates/ci-repair/verification/safety-gate-pr-evidence.md
- templates/ci-repair/verification/validation-evidence.md
What it's allowed to do
9 allowed action(s), 6 forbidden action(s), 5 credential scope(s)
- Allowed actions
- read_ci_status, read_ci_logs, read_repository_files, run_local_tests, modify_test_code, propose_non_test_code_patch, push_repair_branch, open_pull_request, create_follow_up_task
- Credential scopes
- repo:contents:write, pull_request:write, actions:read, checks:read, kanban:create
- Forbidden actions
- disable_required_checks_without_approval, weaken_assertions_without_root_cause_evidence, delete_failing_tests_to_make_ci_green, mask_errors_with_blanket_retries_or_ignores, merge_non_test_code_without_human_diff_review, expose_credentials_in_logs_or_comments