self-healing-loop
A configuration of loop-controller. That skill supplies the loop
machinery — primitive selection, the full guardrail stack, state
externalization. This skill supplies the three things specific to "watch and
heal": a poll cadence over an external error signal, a default-FAIL proof
of the error gone (a re-run, not a hope), and a hard HITL boundary at any
prod-touching or irreversible fix. Read loop-controller for the guardrails;
they're inherited, not repeated here.
Why disable-model-invocation: this loop edits and commits code on its own
and opens PRs on a schedule. You want to type /self-healing-loop (or wire
it under /loop) — not have Claude silently start an autonomous heal loop
because a log line looked alarming.
The 5-part contract
| Part |
This loop |
| trigger |
a scheduled poll tick (via /loop) over an error source — red CI, an error-log query, an alert/Channel push — or an explicit /self-healing-loop |
| action |
ONE pass: poll the source → triage actionable vs noise → if actionable, reproduce + root-cause (hand the hard diagnosis to diagnose-loop) → fix the cause in a branch → verify → open/update a PR; else record a clean-log confirmation |
| proof |
the error is resolved AND verified — the re-run of the failing signal now passes / the log query is clean — default-FAIL: assume UNRESOLVED until the re-run proves it. Artifact = the green re-run output (or, on a quiet tick, a timestamped clean-log confirmation) |
| memory |
heal_log.md (one entry per tick: signal seen, triage verdict, root cause, the PR opened, the verifying re-run) + the fix branch's git history; the last-seen error fingerprint so a known-open error isn't re-triaged each tick |
| stop |
the verifying re-run passes and a PR is open (or the tick is clean) OR iteration cap OR no-progress for N ticks on the same error OR budget cap OR an HITL checkpoint is hit (a prod-touching / irreversible fix) — pause for the human, never proceed |
The proof: the error is gone, and a re-run says so
"Healed" is not "I edited the file the stack trace pointed at." It is the
failing signal re-run and observed passing — the same test re-run green, the
same log query returning zero matches, the same endpoint returning 200. Assume
unresolved until that re-run exists; that is the default-FAIL stance. A fix
that is plausible but never re-verified is a guess, and a guess shipped to a PR
wastes the reviewer's time and your budget.
On a quiet tick (no actionable error), the proof is a clean-log confirmation:
a timestamped heal_log.md entry recording the source polled, the query run, and
the zero/green result. A watcher that records nothing on quiet ticks can't prove
it was actually watching.
The HITL boundary is load-bearing
This loop runs unattended only inside the reversible, hard-verifier-backed
envelope: reproduce in a sandbox, fix on a branch, verify, open a PR. Everything
that touches live state is an HITL checkpoint — pause and surface, never act:
- never auto-deploy a hotfix (even a "one-line" one),
- never auto-merge a PR that fixes a prod error,
- never mutate production data, live infra, secrets, or external APIs to
"heal" — those are human decisions.
The autonomous deliverable is a PR a human merges, not a healed production
system. When the only fix is irreversible (a data backfill, a rollback deploy),
the loop's job ends at: reproduced, diagnosed, fix proposed, human paged. The
full poll-source options, the actionable-vs-noise triage table, and the HITL
boundary table are in references/watch-and-heal.md.
Step 1 — Poll the error source, fingerprint what you see
Each tick, read the configured source (precedence: .claude/profile.yaml if it
declares one, else the project's CI/log tooling — the same feeds the
observability-agent instruments): CI status (gh run list), an error-log
query, or an alert/Channel push. Compute a stable fingerprint for
each error (normalized message + top frame) and compare against the last-seen set
in heal_log.md so a known-open error isn't re-diagnosed every tick. Source
options and example queries: references/watch-and-heal.md.
Step 2 — Triage: actionable vs noise
Not every red line earns a fix. Actionable = a reproducible defect in code
this repo owns, with a fix that fits the reversible envelope. Noise = a flaky
infra blip, a known-open error already in a PR, a third-party outage, or a signal
whose fix is inherently prod-touching (→ straight to HITL). Triage first; one
bad triage spends an entire diagnose cycle on a ghost. The triage table is in the
reference file. A noise tick still gets a heal_log.md entry.
Step 3 — Reproduce and root-cause (delegate the hard part)
For an actionable error, build a fast deterministic reproduction first, then
trace the cause, not the first frame. This is exactly diagnose-loop's
discipline — invoke it, don't re-implement it. Its Phase-1 binary-signal
reproduction is the thing that gets skipped under "just ship the fix" pressure
and the thing that makes the verify in Step 4 meaningful. Record the cause in
heal_log.md.
Step 4 — Fix on a branch, verify, open the PR
Fix the root cause on a dedicated branch (never on the default branch). Then
verify: re-run the failing signal — for a red build, this is where you
delegate to fix-until-green to drive the whole gate (test + lint + typecheck)
clean, not just the one failing test. The verifying re-run output is the
proof. Commit a checkpoint, open or update a PR describing the cause, the fix,
and the green re-run, and stop there — the human merges. Append the tick's
outcome (PR link + verifying run) to heal_log.md.
Guardrails specific to this loop
Inherits the full stack from loop-controller → references/safety.md. The caps
this loop sets:
- Iteration cap — bound the ticks (the
/loop cadence is itself a cap:
it's session-scoped and expires) and the heal attempts within a tick (read
from .claude/profile.yaml if set). Hitting either is a stop-and-escalate.
- No-progress detection — if the same error fingerprint survives N
consecutive ticks without the verifying re-run going green, stop and surface
it. The same production error un-healed three ticks running means the fix is
wrong or the cause is misdiagnosed — page the human, don't keep re-trying.
- Budget cap — a polling watcher can run for hours; each actionable tick
spends a full diagnose + fix cycle. Watch
/cost; terminate at the ceiling.
Quiet ticks should be cheap — don't re-diagnose known-open errors.
- HITL before anything irreversible — the load-bearing boundary above. Any
prod-touching / irreversible fix pauses for the human (
loop-controller
guardrail 4). Never auto-deploy, never auto-merge a prod hotfix.
- Never weaken the signal to make the tick clean. Forbidden, each a finding:
muting an alert, deleting the failing test, narrowing a log query to hide the
error, or marking healed without the verifying re-run. A clean log that came
from silencing the check is not a heal.
Choosing the driver primitive
Per loop-controller Step 1, this is a watch / poll loop, so the primitive is
/loop, not /goal — it watches an external signal change on a cadence
rather than pushing one task to a finish line:
- Canonical —
/loop poller. /loop 30m /self-healing-loop (or the cadence
the source warrants) runs one heal pass per tick. /loop is session-scoped,
expires in ~3 days, and does no catch-up — re-arm it for a standing watcher
(loop-controller's references/primitives.md). For a true always-on schedule
across sessions, promote to a scheduled cloud routine.
- Inside a tick, the heal step degenerates to a finish-line loop — that's
where
fix-until-green (a /goal or Stop-hook loop) drives the red build to
green as this loop's verifier.
Long-run hygiene (wired per loop-controller Step 6)
A watcher that ticks for days needs the Claude 5 long-run rules in its own
contract (drop-in text: model-adaptation → references/long-run-hygiene.md):
- Evidence-backed progress — "healed" is claimed only with the verifying
re-run's output in
heal_log.md; a quiet tick is claimed only with the
timestamped clean-log query result. No entry, no claim.
- Don't end a tick on a promise — a tick ends with a PR opened/updated, a
clean-log entry recorded, or a human paged at the HITL boundary; never with
"I'll diagnose this next tick" and no triage.
- Budget is a harness decision — a poll loop that fires around the clock
adds up; enforce the ceiling from
.claude/profile.yaml in the harness and
keep countdowns away from the working tick.
- Effort per tick — quiet-tick polls run at
low/medium; escalate to
high/xhigh only inside an actionable diagnose/fix cycle (tiering:
model-adaptation).
How this differs from its neighbors
- vs.
diagnose-loop — diagnose-loop is the inner reproduce-and-root-cause
discipline for one hard bug. This is the outer watch→heal→verify→PR cycle: it
adds the polling cadence, the actionable-vs-noise triage, and the prod HITL
boundary, and invokes diagnose-loop for the diagnosis it can't shortcut.
- vs. babysit (review-and-revise) — babysit polls a PR's review comments and
revises the diff. This polls production / CI error signals and opens a new
fix PR. Different source, different deliverable; both are
/loop pollers.
Reference files
references/watch-and-heal.md — the poll-source
options (CI status, error-log queries, an alert/Channel/webhook push) with
example commands, the actionable-vs-noise triage table, and the HITL boundary
table (what runs unattended vs what always pages a human).
1---2name: self-healing-loop3description: Watch a production or CI error signal on a cadence and, when an ACTIONABLE error appears, trace its root cause, fix it in a branch, verify the failing signal now passes, and open a PR for a human to merge — never auto-deploy and never auto-merge a prod hotfix. One pass polls the error source: actionable error present means hand the hard diagnosis to diagnose-loop, fix the cause, verify, open or update a PR; no actionable error means record a clean-log confirmation. Use when you want an unattended watcher over CI status, error logs, alerts, or an observability feed that heals reversible failures and escalates the rest. Trigger on "watch the logs and fix errors", "self-heal CI", "production error sweep", "poll for failures and open a fix PR", "auto-fix the red build", "watch CI and heal it", "error sweep loop", "auto-heal production errors", "/self-healing-loop". A configuration of loop-controller.4---56# self-healing-loop78> **A configuration of [`loop-controller`].** That skill supplies the loop9> machinery — primitive selection, the full guardrail stack, state10> externalization. This skill supplies the three things specific to "watch and11> heal": a **poll cadence** over an external error signal, a **default-FAIL proof12> of the error gone** (a re-run, not a hope), and a **hard HITL boundary** at any13> prod-touching or irreversible fix. Read `loop-controller` for the guardrails;14> they're inherited, not repeated here.15>16> **Why `disable-model-invocation`:** this loop edits and commits code on its own17> and opens PRs on a schedule. You want to *type* `/self-healing-loop` (or wire18> it under `/loop`) — not have Claude silently start an autonomous heal loop19> because a log line looked alarming.2021## The 5-part contract2223| Part | This loop |24|---|---|25| **trigger** | a scheduled poll tick (via `/loop`) over an error source — red CI, an error-log query, an alert/Channel push — or an explicit `/self-healing-loop` |26| **action** | ONE pass: poll the source → triage actionable vs noise → if actionable, reproduce + root-cause (hand the hard diagnosis to [`diagnose-loop`]) → fix the cause in a branch → **verify** → open/update a PR; else record a clean-log confirmation |27| **proof** | the error is **resolved AND verified** — the re-run of the failing signal now passes / the log query is clean — default-FAIL: assume UNRESOLVED until the re-run proves it. Artifact = the green re-run output (or, on a quiet tick, a timestamped clean-log confirmation) |28| **memory** | `heal_log.md` (one entry per tick: signal seen, triage verdict, root cause, the PR opened, the verifying re-run) + the fix branch's git history; the last-seen error fingerprint so a known-open error isn't re-triaged each tick |29| **stop** | the verifying re-run passes and a PR is open (or the tick is clean) **OR** iteration cap **OR** no-progress for N ticks on the same error **OR** budget cap **OR** an HITL checkpoint is hit (a prod-touching / irreversible fix) — pause for the human, never proceed |3031## The proof: the error is gone, and a re-run says so3233"Healed" is **not** "I edited the file the stack trace pointed at." It is the34**failing signal re-run and observed passing** — the same test re-run green, the35same log query returning zero matches, the same endpoint returning 200. Assume36**unresolved** until that re-run exists; that is the default-FAIL stance. A fix37that is plausible but never re-verified is a guess, and a guess shipped to a PR38wastes the reviewer's time and your budget.3940On a quiet tick (no actionable error), the proof is a **clean-log confirmation**:41a timestamped `heal_log.md` entry recording the source polled, the query run, and42the zero/green result. A watcher that records nothing on quiet ticks can't prove43it was actually watching.4445## The HITL boundary is load-bearing4647This loop runs unattended only inside the **reversible, hard-verifier-backed**48envelope: reproduce in a sandbox, fix on a branch, verify, open a PR. Everything49that touches live state is an **HITL checkpoint** — pause and surface, never act:5051- **never auto-deploy** a hotfix (even a "one-line" one),52- **never auto-merge** a PR that fixes a prod error,53- **never** mutate production data, live infra, secrets, or external APIs to54 "heal" — those are human decisions.5556The autonomous deliverable is *a PR a human merges*, not a healed production57system. When the only fix is irreversible (a data backfill, a rollback deploy),58the loop's job ends at: reproduced, diagnosed, fix proposed, **human paged**. The59full poll-source options, the actionable-vs-noise triage table, and the HITL60boundary table are in [`references/watch-and-heal.md`](references/watch-and-heal.md).6162## Step 1 — Poll the error source, fingerprint what you see6364Each tick, read the configured source (precedence: `.claude/profile.yaml` if it65declares one, else the project's CI/log tooling — the same feeds the66[`observability-agent`] instruments): CI status (`gh run list`), an error-log67query, or an alert/Channel push. Compute a stable **fingerprint** for68each error (normalized message + top frame) and compare against the last-seen set69in `heal_log.md` so a known-open error isn't re-diagnosed every tick. Source70options and example queries: [`references/watch-and-heal.md`](references/watch-and-heal.md).7172## Step 2 — Triage: actionable vs noise7374Not every red line earns a fix. **Actionable** = a reproducible defect in code75this repo owns, with a fix that fits the reversible envelope. **Noise** = a flaky76infra blip, a known-open error already in a PR, a third-party outage, or a signal77whose fix is inherently prod-touching (→ straight to HITL). Triage first; one78bad triage spends an entire diagnose cycle on a ghost. The triage table is in the79reference file. A noise tick still gets a `heal_log.md` entry.8081## Step 3 — Reproduce and root-cause (delegate the hard part)8283For an actionable error, **build a fast deterministic reproduction first**, then84trace the *cause*, not the first frame. This is exactly [`diagnose-loop`]'s85discipline — **invoke it**, don't re-implement it. Its Phase-1 binary-signal86reproduction is the thing that gets skipped under "just ship the fix" pressure87and the thing that makes the verify in Step 4 meaningful. Record the cause in88`heal_log.md`.8990## Step 4 — Fix on a branch, verify, open the PR9192Fix the root cause on a dedicated branch (never on the default branch). Then93**verify**: re-run the failing signal — for a red build, this is where you94delegate to [`fix-until-green`] to drive the whole gate (test + lint + typecheck)95clean, not just the one failing test. The verifying re-run output **is** the96proof. Commit a checkpoint, open or update a PR describing the cause, the fix,97and the green re-run, and **stop there** — the human merges. Append the tick's98outcome (PR link + verifying run) to `heal_log.md`.99100## Guardrails specific to this loop101102Inherits the full stack from `loop-controller` → `references/safety.md`. The caps103this loop sets:104105- **Iteration cap** — bound the *ticks* (the `/loop` cadence is itself a cap:106 it's session-scoped and expires) and the heal attempts *within* a tick (read107 from `.claude/profile.yaml` if set). Hitting either is a *stop-and-escalate*.108- **No-progress detection** — if the **same error fingerprint** survives **N109 consecutive ticks** without the verifying re-run going green, stop and surface110 it. The same production error un-healed three ticks running means the fix is111 wrong or the cause is misdiagnosed — page the human, don't keep re-trying.112- **Budget cap** — a polling watcher can run for hours; each actionable tick113 spends a full diagnose + fix cycle. Watch `/cost`; terminate at the ceiling.114 Quiet ticks should be cheap — don't re-diagnose known-open errors.115- **HITL before anything irreversible** — the load-bearing boundary above. Any116 prod-touching / irreversible fix pauses for the human (`loop-controller`117 guardrail 4). Never auto-deploy, never auto-merge a prod hotfix.118- **Never weaken the signal to make the tick clean.** Forbidden, each a *finding*:119 muting an alert, deleting the failing test, narrowing a log query to hide the120 error, or marking healed without the verifying re-run. A clean log that came121 from silencing the check is not a heal.122123## Choosing the driver primitive124125Per `loop-controller` Step 1, this is a **watch / poll** loop, so the primitive is126**`/loop`**, not `/goal` — it watches an external signal change on a cadence127rather than pushing one task to a finish line:128129- **Canonical — `/loop` poller.** `/loop 30m /self-healing-loop` (or the cadence130 the source warrants) runs one heal pass per tick. `/loop` is session-scoped,131 expires in ~3 days, and does no catch-up — re-arm it for a standing watcher132 (`loop-controller`'s `references/primitives.md`). For a true always-on schedule133 across sessions, promote to a scheduled cloud routine.134- **Inside a tick**, the *heal* step degenerates to a finish-line loop — that's135 where [`fix-until-green`] (a `/goal` or Stop-hook loop) drives the red build to136 green as this loop's verifier.137138## Long-run hygiene (wired per loop-controller Step 6)139140A watcher that ticks for days needs the Claude 5 long-run rules in its own141contract (drop-in text: `model-adaptation` → `references/long-run-hygiene.md`):142143- **Evidence-backed progress** — "healed" is claimed only with the verifying144 re-run's output in `heal_log.md`; a quiet tick is claimed only with the145 timestamped clean-log query result. No entry, no claim.146- **Don't end a tick on a promise** — a tick ends with a PR opened/updated, a147 clean-log entry recorded, or a human paged at the HITL boundary; never with148 "I'll diagnose this next tick" and no triage.149- **Budget is a harness decision** — a poll loop that fires around the clock150 adds up; enforce the ceiling from `.claude/profile.yaml` in the harness and151 keep countdowns away from the working tick.152- **Effort per tick** — quiet-tick polls run at `low`/`medium`; escalate to153 `high`/`xhigh` only inside an actionable diagnose/fix cycle (tiering:154 `model-adaptation`).155156## How this differs from its neighbors157158- **vs. [`diagnose-loop`]** — diagnose-loop is the *inner* reproduce-and-root-cause159 discipline for one hard bug. This is the *outer* watch→heal→verify→PR cycle: it160 adds the polling cadence, the actionable-vs-noise triage, and the prod HITL161 boundary, and **invokes** diagnose-loop for the diagnosis it can't shortcut.162- **vs. babysit (review-and-revise)** — babysit polls a *PR's review comments* and163 revises the diff. This polls *production / CI error signals* and opens a *new*164 fix PR. Different source, different deliverable; both are `/loop` pollers.165166## Reference files167168- [`references/watch-and-heal.md`](references/watch-and-heal.md) — the poll-source169 options (CI status, error-log queries, an alert/Channel/webhook push) with170 example commands, the actionable-vs-noise triage table, and the HITL boundary171 table (what runs unattended vs what always pages a human).172173[`loop-controller`]: ../loop-controller/SKILL.md174[`fix-until-green`]: ../fix-until-green/SKILL.md175[`diagnose-loop`]: ../../workflows/diagnose-loop/SKILL.md176[`observability-agent`]: ../../roles/observability-agent/SKILL.md