From f675ae6565e1f588dd7baf2474a27f566b21b7b8 Mon Sep 17 00:00:00 2001 From: Jakob Wennberg <149234542+jakobwennberg@users.noreply.github.com> Date: Thu, 2 Jul 2026 13:36:12 +0200 Subject: [PATCH] fix(loops): committed playbook path + Sentry-first error source (#851) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix(loops): move playbook to committed .claude/loops.md; Sentry-first error source dev_docs/* is gitignored ('internal reference, not published'), so the loop skills' 'read dev_docs/loops.md first' reference never resolves on main / in a fresh cloud clone. Relocate the playbook to .claude/loops.md (committed) and repoint all 4 skills. Rewrite loop-vercel-errors to use the wired Sentry API (the Vercel MCP is not in the cloud routine tool allowlist) and document the required token scope (event:read, project:read) + cloud-env secrets. Co-Authored-By: Claude Opus 4.8 (1M context) * fix(loops): Vercel loop is Vercel-native + local (Sentry is not wired up) Sentry is not integrated in this codebase (no @sentry/* dep, no config, no instrumentation, zero source refs) — the SENTRY_* names in .env.local and CLAUDE.md are leftovers. Rewrite loop-vercel-errors to source errors from Vercel (Vercel MCP locally; Vercel API with VERCEL_TOKEN in cloud) and reclassify it as a LOCAL loop, since the Vercel MCP is only available locally and there is no error-aggregation service. The cloud trigger stays disabled. Only GH_TOKEN is needed to provision the cloud loops (1 & 3). Co-Authored-By: Claude Opus 4.8 (1M context) --------- Co-authored-by: Claude Opus 4.8 (1M context) --- .claude/loops.md | 106 +++++++++++++++++++++ .claude/skills/loop-design-scan/SKILL.md | 2 +- .claude/skills/loop-issue-triage/SKILL.md | 2 +- .claude/skills/loop-pr-ci-triage/SKILL.md | 2 +- .claude/skills/loop-vercel-errors/SKILL.md | 65 ++++++++----- 5 files changed, 149 insertions(+), 28 deletions(-) create mode 100644 .claude/loops.md diff --git a/.claude/loops.md b/.claude/loops.md new file mode 100644 index 00000000..ed1be245 --- /dev/null +++ b/.claude/loops.md @@ -0,0 +1,106 @@ +# Agentic Loops — Playbook + +Proactive loops that scan the codebase and our external systems (GitHub, Vercel), then +**propose** fixes and file well-formed tickets. This file is the shared contract every loop obeys. +Skills under `.claude/skills/loop-*` implement the loops; cloud routines and local `/loop` invocations +run them on a schedule. + +> These are **proactive loops** — triggered by a schedule, no human in real time, each item exits when +> its goal is met. Quality comes from the *system around the loop* (verification skills, clean +> conventions, second-agent review), not from a clever prompt. + +> **This file lives at `.claude/loops.md` (committed).** `dev_docs/*` is gitignored ("internal +> reference, not published"), so the playbook cannot live there — the cloud routines clone `main` and +> need this file present. + +--- + +## Autonomy policy — "Propose, don't merge" + +This is a Swedish accounting/compliance codebase. Loops never touch `main` or production. + +| Loop may… | Loop may **NOT**… | +|---|---| +| Fix trivial/low-risk issues on a `loop/*` branch | Merge any PR (`gh pr merge` is forbidden) | +| Open PRs for review, comment on PRs | Push to `main` or any human's active branch | +| File / label / dedupe / close GitHub issues | Force-push over another author's commits | +| Push to a PR branch it created, or a dependabot branch | Edit posted journal entries / violate an [Accounting Guard Rail](../CLAUDE.md#accounting-guard-rails) | +| Escalate to a human via `loop:needs-human` | Act on PRs from `contributor:flagged` / `pr:flagged` authors | + +Every code change a loop makes **must pass the [`loop-verify`](skills/loop-verify/SKILL.md) gate before +the PR is opened.** No exceptions. + +--- + +## Ticketing & dedupe conventions (all loops share these) + +**Destination:** GitHub Issues + PRs in `erp-mafia/accounted` (via `gh`). Not Linear. + +**Labels:** `loop:auto` (always, on anything a loop creates), `loop:vercel`, `loop:triage`, +`loop:design`, `loop:needs-human` (a loop tried and could not safely proceed). + +**Idempotency / anti-spam — MANDATORY.** Before filing anything: +1. Compute a stable **fingerprint** (error signature, file:line, rule id — never a timestamp). +2. `gh issue list --search " in:body state:all"` (include closed). Match → comment instead + of filing a duplicate; closed + recurring → reopen with a note. +3. Embed `` in the body so future runs find it. + +**Branch naming:** `loop/-` — e.g. `loop/ci-pr848`, `loop/issue-843`, `loop/vercel-`. + +**Anti-thrash:** if the same fix (same fingerprint) already failed, **stop**, label `loop:needs-human`, +comment what was tried. Never retry the same failing action in a cycle. + +**Per-run caps (cost):** each run bounds how much it acts and `log()`s what it skipped. Defaults below. + +--- + +## The loops + +| # | Loop | Skill | Where | Cadence (default) | Per-run cap | +|---|---|---|---|---|---| +| 1 | PR + CI triage | `loop-pr-ci-triage` | **Cloud** `trig_01J2nG7eB9gsdAb9YSGBVwa8` | `0 7,11,15 * * *` UTC | ≤5 PRs | +| 2 | Vercel errors → tickets | `loop-vercel-errors` | **Local** (Vercel MCP); cloud needs `VERCEL_TOKEN`. Trigger `trig_014CmE3gTJ7ErnvL2trPYymu` **disabled** | on-demand / `/loop` | ≤8 issues, ≤2 PRs | +| 3 | Issue triage + easy-fix | `loop-issue-triage` | **Cloud** `trig_017hB94ieGVwreJqHpGRDVoM` | `0 7,15 * * *` UTC | triage all; ≤2 PRs | +| 4 | UI/UX + design scan | `loop-design-scan` | **Local** (`/loop`) | on-demand | ≤1 area, ≤6 findings | + +Loops 1 & 3 are cloud routines (only need `gh`). Loop 2 (Vercel errors) is **local** — the Vercel MCP is +only available locally, and there's no error-aggregation service (Sentry is not used). Loop 4 is +**local** — it needs `npm run dev` + Chrome to render/screenshot the UI. + +--- + +## The verification gate (`loop-verify`) +Before any loop opens a PR: `check:lint` → targeted `vitest` → `test:pg` **iff** a +trigger/RPC/RLS/migration was touched → `check:guards` → the "no core imports from `@/extensions/`" +grep → build if config/types changed. Plus: never violate an +[Accounting Guard Rail](../CLAUDE.md#accounting-guard-rails); keep `sv`/`en` in sync ([i18n](rules/i18n.md)). + +--- + +## Cloud-environment requirements (verify these — they are the usual failure points) + +Cloud routines run in a **fresh session** in the anthropic_cloud env (`env_01R1K99XTZCEptnQ7k955qfN`), +cloning `main`. For them to work: + +1. **`gh` must be authenticated in the cloud env.** Each routine's preflight stops and reports + *"environment not provisioned"* if not. Verify via the completion notification of the first fire. +2. **Cloud routines cannot reach interactively-authenticated MCPs** (Vercel/Supabase plugins are not in + the routine tool allowlist). Loops rely on `gh` (via Bash) + HTTP APIs. +3. **The Vercel-errors loop runs locally** (Vercel MCP). Sentry is **not** used in this codebase — the + `SENTRY_*` names in `.env.local`/CLAUDE.md are leftovers. To run this loop in the cloud instead, set a + `VERCEL_TOKEN` secret on the env and accept that Vercel runtime-log retention is short (recent window + only). `GH_TOKEN` is the one secret loops 1 & 3 actually require (private-repo access; + OAuth-only integration does not work for private repos — anthropics/claude-code#64130). + +--- + +## Operating the loops +- **List / pause / retune:** `/schedule` (or the trigger MCP tools; `update_trigger` for a new cron). +- **Run on-demand:** `/loop-pr-ci-triage`, `/loop-issue-triage`, `/loop-vercel-errors`, + `/loop-design-scan `. Wrap in `/loop ` to repeat locally; `/goal` for a hard exit. +- **Cost:** route mechanical steps to cheaper models; reserve judgment for the strong model. `/usage`. + Don't run more often than the watched thing changes. + +## Extending +When a loop produces a bad result, encode the lesson back into the skill / a CLAUDE.md rule / a verifier +so every future run improves — don't just fix the one output. diff --git a/.claude/skills/loop-design-scan/SKILL.md b/.claude/skills/loop-design-scan/SKILL.md index 0e4d8dab..77fbc282 100644 --- a/.claude/skills/loop-design-scan/SKILL.md +++ b/.claude/skills/loop-design-scan/SKILL.md @@ -7,7 +7,7 @@ description: Local loop that scans a given area of the Accounted UI against the **Goal:** for one area of the app, surface concrete, design-system-grounded UI/UX improvements and file them as GitHub issues (deduped). This is the GitHub-Issues sibling of the `scout-design` skill (which -targets Linear). Read `dev_docs/loops.md` first. **Runs locally** — it needs to render the UI. +targets Linear). Read `.claude/loops.md` first. **Runs locally** — it needs to render the UI. ## Why local A headless cloud session can't see the UI. This loop starts `npm run dev` and drives Chrome to render diff --git a/.claude/skills/loop-issue-triage/SKILL.md b/.claude/skills/loop-issue-triage/SKILL.md index 294c75ab..8f788708 100644 --- a/.claude/skills/loop-issue-triage/SKILL.md +++ b/.claude/skills/loop-issue-triage/SKILL.md @@ -6,7 +6,7 @@ description: Proactive loop that keeps GitHub Issues in erp-mafia/accounted tidy # loop-issue-triage **Goal:** the open-issue list is accurately labeled and free of stale/duplicate/already-fixed items, -and a couple of small fixes ship as PRs each run. **Never merge.** Read `dev_docs/loops.md` first. +and a couple of small fixes ship as PRs each run. **Never merge.** Read `.claude/loops.md` first. ## Preflight `gh auth status`, repo `erp-mafia/accounted`. If it fails, stop and report "environment not provisioned". diff --git a/.claude/skills/loop-pr-ci-triage/SKILL.md b/.claude/skills/loop-pr-ci-triage/SKILL.md index 9625a0f1..d4b57a02 100644 --- a/.claude/skills/loop-pr-ci-triage/SKILL.md +++ b/.claude/skills/loop-pr-ci-triage/SKILL.md @@ -6,7 +6,7 @@ description: Proactive loop that watches open PRs in erp-mafia/accounted, fixes # loop-pr-ci-triage **Goal:** every open, non-draft PR authored by our team or dependabot is either green + review-addressed, -or explicitly escalated with `loop:needs-human`. **Never merge.** Read `dev_docs/loops.md` first. +or explicitly escalated with `loop:needs-human`. **Never merge.** Read `.claude/loops.md` first. ## Preflight - `gh auth status` and confirm repo `erp-mafia/accounted`. If either fails, stop and report "environment not provisioned". diff --git a/.claude/skills/loop-vercel-errors/SKILL.md b/.claude/skills/loop-vercel-errors/SKILL.md index 32fcbe8f..3b78982f 100644 --- a/.claude/skills/loop-vercel-errors/SKILL.md +++ b/.claude/skills/loop-vercel-errors/SKILL.md @@ -1,38 +1,49 @@ --- name: loop-vercel-errors -description: Proactive loop that pulls recent production runtime errors from Vercel (and Sentry if configured), groups + dedupes them, files well-formed GitHub issues, and opens a fix PR only for clearly trivial/safe cases. Use on a schedule (cloud routine) or on-demand via /loop-vercel-errors. Follows dev_docs/loops.md. +description: Loop that pulls recent production runtime errors from Vercel (via the Vercel MCP locally, or the Vercel API with a token), groups + dedupes them, files well-formed GitHub issues, and opens a fix PR only for clearly trivial/safe cases. Best run LOCALLY (the Vercel MCP is available there). Follows .claude/loops.md. --- # loop-vercel-errors **Goal:** every distinct, real production runtime error is tracked as a GitHub issue (deduped), and the -obviously-trivial ones have a proposed fix PR. **Never merge.** Read `dev_docs/loops.md` first. +obviously-trivial ones have a proposed fix PR. **Never merge.** Read `.claude/loops.md` first. -Project: Vercel `erp-base` (`prj_zOvCFaOMXS166cUY5VYEGHKke00X`, team `team_WPj3QZgcSVRWZKcHJQB3wfv8`). +Vercel project `erp-base` (`prj_zOvCFaOMXS166cUY5VYEGHKke00X`, team `team_WPj3QZgcSVRWZKcHJQB3wfv8`). -## 1. Fetch errors (try sources in order; report which worked) -1. **Vercel MCP** (`mcp__plugin_vercel_vercel__*`) — authenticate if needed, then pull recent runtime - logs / observability for the production deployment. Prefer this. -2. **Vercel CLI** fallback: `vercel logs --json` (tails a window) or - `vercel inspect`. The CLI here is old (v48) — if it errors, note it and move on. -3. **Sentry** (the app ships `@sentry/*`; `SENTRY_DSN` may be set) — if a Sentry token is available, - its issue stream is richer than Vercel logs. Use it if present. +> **No error-aggregation service is wired up** (Sentry is NOT used — no `@sentry/*`, no config, despite +> leftover `SENTRY_*` names in `.env.local`/CLAUDE.md). The only source is **Vercel's own runtime logs / +> observability**, which have short retention — this loop sees a *recent window*, not full history. +> **Run it LOCALLY** so the Vercel MCP is available; the cloud routine is disabled (see step 1). -If **no** source is reachable in this environment, stop and report that clearly (do not invent errors). +## 1. Fetch errors from Vercel +1. **Vercel MCP (primary, local runs):** `mcp__plugin_vercel_vercel__*` — authenticate if needed, then + list recent **production** deployments and pull runtime logs / observability / error events. This is + the clean path and is only present in a LOCAL session. +2. **Vercel API (if a token exists):** with a `VERCEL_TOKEN` (from vercel.com/account/tokens), + ```bash + curl -s -H "Authorization: Bearer $VERCEL_TOKEN" \ + "https://api.vercel.com/v6/deployments?projectId=prj_zOvCFaOMXS166cUY5VYEGHKke00X&teamId=team_WPj3QZgcSVRWZKcHJQB3wfv8&limit=5&target=production" + # then pull runtime logs for the latest prod deployment id + ``` + This is the only path that works in a **cloud** routine — and only if `VERCEL_TOKEN` is set as a + secret on the cloud env. `VERCEL_OIDC_TOKEN` in `.env.local` is short-lived and NOT a usable API token. +3. **Vercel CLI last resort:** `vercel logs --json` (v48 here; only a live tail). + +If **no** source is reachable, STOP and report exactly which sources you tried and why each failed. +**Never invent errors.** ## 2. Group into distinct errors -Cluster raw log lines by **signature**: normalized message + top of stack + route. Drop noise -(expected 4xx, aborted requests, health checks). For each cluster capture: signature, first/last seen, -count, sample stack, affected route/file. +Cluster raw log lines by **signature**: normalized message + top of stack + route. Drop noise (expected +4xx, aborted requests, health checks). For each cluster capture: signature, first/last seen, count, +sample stack, affected route/file. ## 3. Dedupe (mandatory — see loops.md) -Fingerprint = hash of the normalized signature. +Fingerprint = a stable hash of the normalized signature. ```bash gh issue list --search " in:body state:all" --json number,state ``` -- Match open → add a comment ("still occurring, N times since "). Do not file a duplicate. -- Match closed but recurring → reopen with a note. -- No match → file new (below). **Cap 8 new issues/run**; log the rest. +Open match → comment ("still occurring, N since "). Closed + recurring → reopen. No match → file +new. **Cap 8 new issues/run**; log the rest. ## 4. File the issue ``` @@ -40,17 +51,21 @@ Title: [prod error] () Labels: loop:auto, loop:vercel, bug Body: **Signature:** … **Count / window:** … **First/last seen:** … - **Route/file:** app/…:line **Sample stack:** (fenced) - **Likely cause:** <1–2 lines of hypothesis from the code> + **Route/file:** app/…:line **Sample stack:** (fenced) **Vercel:** + **Likely cause:** <1–2 lines from reading the referenced code> ``` Point at the suspected `file:line` by reading the code the stack references. ## 5. Trivial fix only (propose-don't-merge) — cap 2 PRs/run -Open a `loop/vercel-` fix PR **only** when the cause is unambiguous and low-risk (e.g. a missing -null guard, an unhandled `undefined`, a bad `.env` read with an obvious default, a narrow type fix). -Anything touching bookkeeping/money/migrations/auth → issue only, label `loop:needs-human`. -Every fix must pass the **[`loop-verify`](../loop-verify/SKILL.md)** gate. PR body: `Closes #`. +Open a `loop/vercel-` fix PR **only** when the cause is unambiguous and low-risk (missing null +guard, unhandled `undefined`, bad env read with an obvious default, a narrow type fix). Anything +touching bookkeeping/money/migrations/auth → issue only, label `loop:needs-human`. Every fix passes the +**[`loop-verify`](../loop-verify/SKILL.md)** gate. PR body: `Closes #`. ## 6. Report -List: new issues filed, existing issues updated, fix PRs opened, sources used, anything skipped. +List: new issues filed, existing issues updated, fix PRs opened, which source worked, anything skipped. +``` + +> **Want proper error tracking?** Vercel runtime-log retention is short. For a real errors→tickets loop, +> wire an error sink (Sentry, Vercel Log Drains to a store, etc.) and update step 1 to read from it.