diff --git a/.claude/skills/artifact-audit/SKILL.md b/.claude/skills/artifact-audit/SKILL.md index 27b500e..430e002 100644 --- a/.claude/skills/artifact-audit/SKILL.md +++ b/.claude/skills/artifact-audit/SKILL.md @@ -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/` diff --git a/README.md b/README.md index 8304c76..6f0a289 100644 --- a/README.md +++ b/README.md @@ -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. | diff --git a/runbooks/babysit-prs.md b/runbooks/babysit-prs.md index 895d470..bfc2c7e 100644 --- a/runbooks/babysit-prs.md +++ b/runbooks/babysit-prs.md @@ -13,7 +13,9 @@ 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. @@ -21,11 +23,15 @@ 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 @@ -33,3 +39,7 @@ Routines** instead of session-scoped `/loop`: 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. diff --git a/runbooks/staying-current.md b/runbooks/staying-current.md index 016d1c9..b9aaf9c 100644 --- a/runbooks/staying-current.md +++ b/runbooks/staying-current.md @@ -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 @@ -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 @@ -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. | @@ -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. @@ -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 @@ -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): @@ -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. @@ -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. diff --git a/templates/goal/recipes.md b/templates/goal/recipes.md index 5937801..5c9e759 100644 --- a/templates/goal/recipes.md +++ b/templates/goal/recipes.md @@ -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). diff --git a/templates/loop/loop.md b/templates/loop/loop.md index c2fb7b8..81de616 100644 --- a/templates/loop/loop.md +++ b/templates/loop/loop.md @@ -6,8 +6,10 @@ `/loop ` 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 diff --git a/templates/ui-audit/FINDINGS.md b/templates/ui-audit/FINDINGS.md index 45b4595..98e7e51 100644 --- a/templates/ui-audit/FINDINGS.md +++ b/templates/ui-audit/FINDINGS.md @@ -1,7 +1,8 @@ # UX findings