Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 4 additions & 2 deletions .claude/skills/artifact-audit/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,8 +95,10 @@ A scheduled **one-shot** (one trigger → one audit pass → one PR → exit), n
The old per-session `CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION` total cap was
**removed in v2.1.224 (Aug 7 2026)** and can no longer be relied on — there is
no native cap on a session's *total* lifetime spawns.
- **Budget** — the subscription / daily-run cap is the ceiling, real only with
metered overage off (see `runbooks/staying-current.md` → "Budget & caps").
- **Budget** — the subscription usage limit is the ceiling (the platform's own
scheduling caps are *hourly*, and an over-limit run waits rather than fails),
real only with metered overage off (see `runbooks/staying-current.md` →
"Budget & caps").
- **Stall** — N/A for a one-shot; the no-op exit is the termination.
- **No auto-apply.** Unlike `update-knowledge` step 7, nothing here is
auto-applied — every change is human-reviewed. It never edits `knowledge/`
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ written down and kept current.
|------|------------|
| [`knowledge/`](knowledge/) | Living knowledge base — primer, curated sources, changelog, and an `archive/` for resolved/superseded material. Kept current by the `update-knowledge` skill. |
| [`.claude/skills/`](.claude/skills/) | Reusable, invocable skills: `new-loop`, `loop-guardrails`, `update-knowledge` (weekly knowledge refresh), `artifact-audit` (monthly drift check on the repo's own artifacts). |
| [`templates/`](templates/) | Runnable starters — ralph bash loop, `/goal` recipes, `/loop` maintenance prompt. |
| [`templates/`](templates/) | Runnable starters — ralph bash loop, `/goal` recipes, `/loop` maintenance prompt, overnight UI-audit kit. |
| [`guardrails/`](guardrails/) | The three hard stops: budget/iteration defaults + a preflight checklist. |
| [`runbooks/`](runbooks/) | Step-by-step playbooks for common loops. |

Expand Down
18 changes: 14 additions & 4 deletions runbooks/babysit-prs.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,23 +13,33 @@ In a Claude Code session on the branch with the PR:

`/loop` self-paces (1 min–1 hr) and runs cron under the hood, but **only while
the session is open and idle** — close the terminal and it stops. Recurring
tasks expire after 7 days. Stop early with `Esc`.
tasks expire after 7 days. Stop a self-paced loop early with `Esc` while it
waits; a fixed-interval loop (`/loop 30m …`) is cancelled by asking Claude
(`Esc` and `Ctrl+C` don't stop it).

Customize the default behavior by dropping a copy of `templates/loop/loop.md`
at `.claude/loop.md` in the target repo.

## Laptop-closed — Claude Code on the web

For PR-tending that survives your laptop closing, use **Claude Code on the web /
Routines** instead of session-scoped `/loop`:
cloud scheduled tasks** (formerly Routines) instead of session-scoped `/loop`:

- The cloud auto-fix-PR feature subscribes to GitHub PR webhooks and pushes
- The cloud auto-fix-PR feature subscribes to GitHub PR activity and pushes
fixes for failed checks and review comments (requires the Claude GitHub App).
- Enable via the web CI bar, `/autofix-pr` from the terminal, or mobile.
- Turn it on from the PR's **⋮** menu in the session's **Changes** card at
claude.ai/code, with `/autofix-pr` from the terminal while on the PR's branch,
from the mobile app, or by pasting the PR URL into any session and asking.
- It can't react to merge conflicts (GitHub emits no webhook for them) — open
the session and ask for a rebase.

## Guardrails

- Let it fix **build/CI and unambiguous review comments**; for anything
architecturally significant, it should leave a note and ask, not guess.
- Scope it to **your** PRs / the current branch.
- It pushes to the working branch only — never main.
- Auto-fix replies to review threads **as your GitHub account** (labeled as
Claude Code). If the repo runs comment-triggered automation (Atlantis,
`issue_comment` Actions), those replies can trigger it — review that before
enabling, and skip auto-fix where a PR comment can deploy infrastructure.
65 changes: 41 additions & 24 deletions runbooks/staying-current.md
Original file line number Diff line number Diff line change
Expand Up @@ -48,8 +48,10 @@ no-op if nothing changed.
## Scheduled (recommended) — cloud Routine

To make it self-maintaining with your laptop closed, schedule it as a **cloud
Routine** (Claude Code on the web). Routines run on Anthropic's cloud, need no
open session, and support intervals down to ~1 hour — use weekly here.
scheduled task** (formerly "Routine" — the docs, the `/routines` alias, and the
claude.ai/code/routines URL still carry the old name, which this runbook keeps
for continuity). Routines run on Anthropic's cloud, need no open session, and
support intervals down to ~1 hour — use weekly here.

> **Prerequisite:** a Routine clones the repo's **default branch** (`main`) on
> every run, so the `update-knowledge` skill and `knowledge/` must be merged to
Expand All @@ -61,7 +63,8 @@ Routines can't be created from inside a Claude Code *web* session (the
`/schedule` command is disabled there). Use one of:

- **Web/Desktop:** go to [claude.ai/code/routines](https://claude.ai/code/routines)
→ **New routine**, and fill in the fields below.
→ **New task** (some accounts still show **New routine**), and fill in the
fields below.
- **CLI (local session only):**
```
/schedule weekly on Monday at 8am, run the /update-knowledge skill for the loops repo and open a PR with any changes
Expand All @@ -75,7 +78,7 @@ Routines can't be created from inside a Claude Code *web* session (the
| **Repository** | `espi/loops` |
| **Trigger** | Schedule → Weekly (e.g. Monday 08:00 local) |
| **Environment / network** | Needs web research. **Trusted** (default) lets `WebSearch` work but blocks `WebFetch` to arbitrary domains; set **Full** (or **Custom** allowing `code.claude.com`, `platform.claude.com`, `github.com`, `simonwillison.net`, `anthropic.com`) so it can read primary docs. |
| **Branch pushes** | Leave default (`claude/`-prefixed only) — this forces a PR instead of touching `main`. The skill opens its PR from a `claude/knowledge-update-*` branch, so it stays inside this restriction; **do not** enable "Allow unrestricted branch pushes" (that would remove the floor). |
| **Branch pushes** | The `claude/` prefix is the run's **default push target, not an enforced restriction** — the docs state a run pushes to a `claude/`-prefixed branch *"unless your prompt directs it to push to another branch"*, and point to **GitHub branch protection rules or rulesets** as the way to control which branches a run can push to. So: add a **ruleset on `main`** that the connected GitHub identity cannot bypass (a rule that identity can bypass doesn't block a run's push). The skill's prompt never directs pushes elsewhere and its branch is `claude/knowledge-update-*` — keep that convention, but treat the ruleset as the control. |
| **Env vars** | `CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS=6` and `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH=1` (bound the ~5-agent fan-out and forbid recursion); optionally `CLAUDE_CODE_MAX_WEB_SEARCHES_PER_SESSION=100`. **Corrected 2026-09-07:** this row previously said `CLAUDE_CODE_MAX_SUBAGENTS_PER_SESSION=12` — that variable was **removed in v2.1.224** and setting it now does nothing. There is no native per-session *total* spawn cap any more; concurrency + depth are what remain. |
| **Connectors** | None required; remove extras. The PR is opened as your GitHub identity. |

Expand All @@ -90,11 +93,13 @@ they only hold if the Routine is configured to let them:
Note they bound *width and recursion, not lifetime total* — no native
per-session spawn cap exists since v2.1.224. Keep the schedule **weekly**,
not hourly.
- **Budget:** the account subscription usage limit + the per-account daily
routine-run cap *reject* runs when exhausted — a real ceiling **only if
metered overage / usage credits is OFF** for the account (otherwise spend
silently spills onto metered billing instead of stopping). Keep overage off,
or set a spend alert, so this routine can't quietly run up cost. A true
- **Budget:** the account subscription usage limit *rejects* further runs when
exhausted — a real ceiling **only if metered overage / usage credits is OFF**
for the account (otherwise spend silently spills onto metered billing instead
of stopping). The platform's own scheduling caps are **hourly, not daily**
(100 scheduled runs/hour per account), and an over-limit run **waits** for
the reset rather than failing — pacing, not a ceiling. Keep overage off, or
set a spend alert, so this routine can't quietly run up cost. A true
*per-run dollar* ceiling isn't available for cloud Routines — that needs the
Agent SDK's `max_budget_usd`; the account-limit + subagent-cap + one-pass
structure is the realistic envelope here.
Expand All @@ -103,9 +108,11 @@ they only hold if the Routine is configured to let them:

### Prompt (paste verbatim)

Routines run autonomously with no approval prompts, so the prompt must be
self-contained — but it should point at the skill file rather than embed a
frozen copy of its steps. **This bit us once already**: an earlier version of
Routine runs start in **auto mode** with no human at the terminal; an action
that matches an ask rule, or repeated blocks, raises a permission prompt that
**waits in the run's session** until someone answers it — so the prompt must be
self-contained, steer clear of ask-rule territory, and it should point at the
skill file rather than embed a frozen copy of its steps. **This bit us once already**: an earlier version of
this prompt spelled out the procedure inline, the skill later gained two new
steps (re-checking the standing re-verify backlog, archiving resolved
caveats), and the Routine kept firing the stale embedded steps because a
Expand Down Expand Up @@ -141,8 +148,8 @@ weekly, with these differences:
| Field | Value |
|---|---|
| **Name** | Monthly loops artifact audit |
| **Trigger** | Schedule → there is no Monthly preset (presets are hourly / daily / weekdays / weekly): pick the closest preset (Weekly), then run `/schedule update` from a local CLI session to set a monthly cron (e.g. 1st of the month, 08:00 local) |
| **Everything else** | Same as the weekly (repo `espi/loops`; env with `CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS=6` and `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH=1`; **Full/Custom** network for primary-doc checks; `claude/`-only branch pushes; overage off). See "Budget & caps" above. |
| **Trigger** | Schedule → there is no Monthly preset: create the task, then select **Custom** in its edit dialog (web) — or run `/schedule update` from a local CLI session — and set a monthly cron (e.g. 1st of the month, 08:07; a few minutes past the hour avoids the documented late starts on :00) |
| **Everything else** | Same as the weekly (repo `espi/loops`; env with `CLAUDE_CODE_MAX_CONCURRENT_SUBAGENTS=6` and `CLAUDE_CODE_MAX_SUBAGENT_SPAWN_DEPTH=1`; **Full/Custom** network for primary-doc checks; branch-protection ruleset on `main` in place; overage off). See "Budget & caps" above. |

Prompt (paste verbatim — points at the skill file, same anti-staleness reason as
the weekly):
Expand Down Expand Up @@ -219,13 +226,19 @@ manipulated:
3. **The fence is machine-checked, not self-graded.** `self-edit-guard` enforces
scope, protected regions and the per-pass cap in CI. A compromised agent
asserting "this edit is in scope" changes nothing.
4. **Branch pushes are `claude/`-prefixed only**, so it physically cannot touch
`main`.
4. **Pushes land on `claude/`-prefixed branches by convention, and `main` is
guarded by GitHub branch protection.** The prefix is only the run's default
push target (a prompt can direct pushes elsewhere), so the enforcement is a
ruleset on `main` that the connected GitHub identity cannot bypass —
configure it (see the Branch pushes row above); the prefix alone is not a
floor.

**What would invalidate this.** Treat any of these as a stop-and-rethink, not a
config tweak:

- Enabling unrestricted branch pushes, or making the routine able to merge.
- Prompting the routine to push to a non-`claude/` branch, dropping (or
granting bypass to) `main`'s branch-protection ruleset, or making the routine
able to merge.
- Widening the auto-edit envelope beyond `update-knowledge/SKILL.md`, or
removing `self-edit-guard` as a check.
- Giving the routine connectors or credentials that reach beyond this repo.
Expand All @@ -252,10 +265,14 @@ by `.github/workflows/self-edit-guard.yml`. To make that a *hard* merge gate
rather than a red X you have to notice, add `self-edit-guard` as a **required
status check** in `main`'s branch-protection rules.

> **Branch prefix — reconciled.** Step 8 of the skill opens the PR from a
> `claude/knowledge-update-YYYY-MM-DD` branch, which sits inside the Routine's
> default `claude/`-only push restriction — so the "human merges every PR"
> floor holds without enabling unrestricted pushes. Keep it that way: if you
> ever rename the skill's branch, keep the `claude/` prefix, or the routine
> will lose the ability to push (or you'll be tempted to disable the
> restriction, which removes the floor).
> **Branch prefix — reconciled (corrected 2026-10-25).** Step 8 of the skill
> opens the PR from a `claude/knowledge-update-YYYY-MM-DD` branch. An earlier
> version of this note called the `claude/` prefix a "default push
> restriction" the routine physically couldn't escape — the current docs say
> otherwise: a run pushes to a `claude/`-prefixed branch *"unless your prompt
> directs it to push to another branch"*, and branch control is done with
> **GitHub branch protection rules or rulesets**. So the "human merges every
> PR" floor is held by two things you own: the skill's prompt (which never
> directs a push elsewhere — keep the `claude/` prefix if you ever rename the
> branch) and a **ruleset on `main`** that the connected GitHub identity
> cannot bypass. Configure the ruleset; don't rely on the prefix.
25 changes: 18 additions & 7 deletions templates/goal/recipes.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,36 +11,47 @@ own output**, not from hidden state.
- Make it **checkable from surfaced output**: have the agent run the check and
print the result, then phrase the condition around that.
- Make it **specific and binary** — no "make it good".
- Keep it **reachable** — an impossible condition loops until you stop it.
- Max condition length: ~4,000 characters.
- Keep it **reachable** — the evaluator clears a goal it judges *impossible*
(recorded as a failed entry), and a run that stops making progress (no tool
use for several turns) pauses with the goal still set; but a condition that is
merely unreachable in practice can burn many turns before either trips, so
bound it (next bullet).
- **Bound it**: include a turn or time clause in the condition itself, e.g.
`… or stop after 20 turns`. Claude reports progress against the clause and
the evaluator judges it from the conversation — so it's a stop condition the
agent narrates, not an enforced cap. On headless runs, add real external
caps too: `claude -p "/goal …" --max-turns N --max-budget-usd X` (both
print-mode flags).
- Max condition length: 4,000 characters.

## Recipes

**Tests + lint green**
```
/goal all tests in test/auth pass and `npm run lint` reports zero errors, and you have shown the passing output
/goal all tests in test/auth pass and `npm run lint` reports zero errors, and you have shown the passing output — or stop after 20 turns
```

**Typecheck clean across the package**
```
/goal `tsc --noEmit` prints no errors and you have pasted the clean output
/goal `tsc --noEmit` prints no errors and you have pasted the clean output — or stop after 20 turns
```

**Migration complete**
```
/goal every call site of the old `getUser()` API has been migrated to `fetchUser()`, `rg "getUser\("` returns no results, and the test suite passes
/goal every call site of the old `getUser()` API has been migrated to `fetchUser()`, `rg "getUser\("` returns no results, and the test suite passes — or stop after 30 turns
```

**Changelog / docs coverage**
```
/goal CHANGELOG.md has an entry for every PR merged this week, and you have listed the PRs and matched each to its entry
/goal CHANGELOG.md has an entry for every PR merged this week, and you have listed the PRs and matched each to its entry — or stop after 15 turns
```

## Operating it

- Status: `/goal` · Stop: `/goal clear` (aliases: stop, off, reset, none,
cancel; `/clear` also removes the active goal)
- Interrupt a run: `Ctrl+C`
- Interrupt a headless (`claude -p`) run: `Ctrl+C`; in an interactive session,
stop with `/goal clear`
- Headless: `claude -p "/goal CHANGELOG.md has an entry for every PR merged this week"`
- Requires the trust dialog accepted; unavailable if `disableAllHooks` /
`allowManagedHooksOnly` is set (the evaluator is part of the hooks system).
Expand Down
6 changes: 4 additions & 2 deletions templates/loop/loop.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
`/loop <prompt>` to let Claude self-pace (1 min–1 hr).

Reminder: /loop is SESSION-SCOPED — it only fires while Claude Code is running
and idle, and stops when the terminal closes. For laptop-closed scheduling use
a cloud Routine instead. Recurring tasks auto-expire after 7 days.
and idle, and stops when the terminal closes (backgrounding the session carries
/loop tasks over to a background session; a self-paced loop is NOT restored on
--resume — run /loop again). For laptop-closed scheduling use a cloud scheduled
task (formerly Routine) instead. Recurring tasks auto-expire after 7 days.
-->

# Loop maintenance prompt
Expand Down
3 changes: 2 additions & 1 deletion templates/ui-audit/FINDINGS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
<!--
UX findings log — the "report" half of the audit. The loop appends a block per
subjective issue (discoverability, dead-ends, CTAs, copy, empty/error states).
This is your morning review queue. Rename to FINDINGS.md in your project.
This is your morning review queue. Copy it into your project's ui-audit/ dir
as-is; the loop appends entries below.
-->

# UX findings
Expand Down
Loading