Loop template

tested 2026-07-07 ✓catalog data (JSON)

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.md

CI 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, and pull_request:write scopes, 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:

  1. failure analysis naming the failed check, failing command, relevant log excerpt, branch, and SHA;
  2. a minimal pull request diff tied to that root cause;
  3. local verification output when local reproduction is possible;
  4. 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:

  • cron polls a configured repository query and creates a repair task or issue when it finds failed checks. It does not merge repairs.
  • github_actions dispatches from workflow_run or workflow_dispatch, collects failed run metadata, and opens a repair issue/task. The patch still goes through a normal PR.
  • agent_kanban starts 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.type is ci_event with event: check_suite_failure.
  • Safe automation: README lists allowed and forbidden actions; permissions.allowed_actions and permissions.forbidden_actions carry the same boundaries.
  • Human gate: README requires review for non-test production code; human_gates.required_for includes merging any non-test production code patch and related masking risks.
  • Verification: README requires analysis, diff, local verification, CI rerun, and review evidence; verification.required names each requirement.
  • Rollback: README says revert/reset the repair branch and retain audit evidence; failure_policy.rollback says the same.

The job description (loop.yaml)

View raw loop.yaml

templates/ci-repair/loop.yaml

spec_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 agents

Agent 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.py

Expected 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.yaml before 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')}")
PY

Expected 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 -n fails — store a GitHub token in the backend secret store before using API-based backends.
  • curl returns 401/403 — token lacks actions:read or 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/exclude

Edit .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.json

Failure 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.env permissions and the set -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.yml

Expected diff includes:

permissions:
  actions: read
  contents: read
  issues: write

After 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 1

Expected 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:

  • gh unavailable 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.md

Expected 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

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