0% 5h quota used · resets in 2h live · 08/09/2026 12:12 CEST

_global:sentry-triage

Weekly Sentry triage of the cms, telecom and renovation repos — fix what is safely code-fixable, one draft PR per repo, journal the rest. Ignores third-party noise.

Runs 7
last 6d ago
Failed 0
no current streak
Median run $4.43
8m · _global ceiling $15.00 per run
Spent in total $32.71
533 turns of agent work

Manifest

_global/tasks.yaml
Address
_global:sentry-triage — also global:sentry-triage
Runner
claude-code
Policy
pr may push a countryos/* branch and open a draft PR — the review is the gate
Workspace
countries/_global/
Also reaches
cms, telecom, renovation, comparator-core
Gate
sentry worklist --repos cms,telecom,renovation
Schedule
0 6 * * 3 · next 09/09/2026 06:00
Model
sonnet
Budget
180 turns · 110 minutes
Triggerable
yes — free text can reach it
Notifies
every run — 🟢 on success, 🔴 on failure
Description (manifest)
Weekly Sentry triage of the cms, telecom and renovation repos — fix what is safely code-fixable, one draft PR per repo, journal the rest. Ignores third-party noise.

Cost per run

oldest left · ceiling $15.00
_global per-run ceiling $15.00 12/08/2026 09:34: $5.8619/08/2026 06:06: $2.9826/08/2026 06:08: $4.4326/08/2026 16:39: $027/08/2026 12:58: $3.7227/08/2026 13:40: $7.7627/08/2026 13:41: $031/08/2026 11:02: $3.0502/09/2026 06:13: $4.91 12/0819/0826/0826/0827/0827/0827/0831/0802/09
WhenWhat it reportedTurnsTookCost
02/09/2026 06:13Confirmed: draft, open, targeting `develop`. Everything is in order. ## Summary Worked the gate's 9-issue worklist across cms, telecom, and renovation. **cms** — opene…9612m$4.91ok
31/08/2026 11:02Both draft PRs are open and memory is recorded. Final summary for the report. **Sentry triage complete.** Worked the given 5-issue worklist across cms and telecom (renov…536m$3.05ok
27/08/2026 13:41no log2.4s$0skipped
27/08/2026 13:40All three repos are confirmed clean, and the final state matches what's reported: cms and renovation are back on `develop` with no local changes, telecom is on the pushed…13636m$7.76ok
27/08/2026 12:58That tool is for `/loop` mode, not applicable here — the background agents will notify me automatically on completion. I'll wait.234m$3.72ok
26/08/2026 16:39no log1.0s$0skipped
26/08/2026 06:08Everything is committed, pushed, and the draft PR is open. Both memory files (`sentry-triage.md` and `journal.md`) are updated in the `_global` workspace, which is a sepa…678m$4.43ok
19/08/2026 06:06Journal entry appended. Run complete. **Outcome:** Opened draft PR [#16305](https://github.com/Selectra-Dev/cms/pull/16305) with 2 fixes — CMS-14A, a fast-growing (1162…546m$2.98ok
12/08/2026 09:34PR confirmed open as a draft against `develop`. Everything's recorded — memory files updated, journal entry appended. **Summary:** Ran a fresh Sentry triage across the w…10412m$5.86ok
Read the skill

Sentry triage — cms, telecom, renovation

You are in the _global workspace. The repos to triage are the ones listed in the REPOS block of your prompt: each is a full git clone, with the branch to work from and the Sentry project that reports on it.

Your prompt also carries a GATE block. A probe already ran sentry worklist and produced the worklist for this run — every issue not yet recorded in triage memory, every one whose event rate says it is still bleeding, and every REPORT a human has left unacted for a month. Work that list. Do not run sentry list to build your own: the gate is what decided this run should happen at all, and a run that widens its own scope is doing work nobody admitted.

The sentry CLI (on PATH; fallback /app/tools/bin/sentry) is your only Sentry access. Every command you use is read-only: never resolve, ignore, assign or comment on Sentry issues, and never run sentry resolve. Closing an issue is not a triage decision — it is settled after the fact by _global:sentry-verify, which checks the fix is in the running build and that the errors then stopped. Your job is to record the fix ref (step 5) so that check can happen at all. Pass --project <slug> to look at a specific repo's project; the slugs are in the REPOS block.

Step 0 — Load triage memory

Read memory/review-feedback.md — binding rules from human review of past PRs, and they apply to every repo, not just the one they were learned in. A fix that violates one will be rejected, so classify such issues REPORT instead.

Then read memory/sentry-triage-<repo>.md for each repo you are triaging. The gate already used these to build the worklist, so you do not need them to decide what to look at — you need them for context on what past runs concluded, and you will append to them in step 4.

Step 1 — Take the worklist

For each issue on the gate's list, get the detail you need:

sentry show  <shortId> --project <slug>
sentry trace <shortId> --project <slug>
sentry urls  <shortId> --project <slug>

urls shows which sites and pages are affected — that goes in the triage record and the PR body. An issue the gate marked REGRESSED or UNFIXED was triaged before: read its old record first, because "we already decided this was infra", "we fixed this and it came back", and "nobody ever acted on this" need different answers. UNFIXED means the row is still classed REPORT a month later and still growing — so the question is not "what is this", it is "why has nothing happened, and can this run do better than the last one did". A line carrying register=OS-nnn is already in decisions.md: read that entry before writing a new one, and if its diagnosis no longer matches what you can see, say so — a wrong entry a human already answered is worse than no entry.

A line marked FIX-FAILED is the one to slow down on. It means a past run's fix is running in production and the errors did not stop — that has been checked against the deployed build, not assumed from a merge. So the previous diagnosis was wrong, or incomplete, and the answer is almost never to apply the same shape of fix again. Re-read the current sentry trace from scratch, read the old fix's diff, and say plainly what the last run got wrong. If you cannot better it, it is a REPORT — a second failed guess is worse than an honest escalation.

If the gate's output ends in a [TRUNCATED: ...] marker, some of its list never reached you. Work what you have and say so in your final report — do not present a partial list as the whole board.

Step 2 — Classify

Every candidate gets exactly one class:

  • FIX — root cause is in repo code, the failing scenario is unambiguous from the event data, and the fix is small and low-risk: missing null/empty checks, bad array keys, type mismatches, missing route params, malformed entries in data files. An unhandled third-party failure counts as FIX when the right fix is graceful handling in our code (e.g. an upstream API times out and the page 500s instead of degrading gracefully) — but read review-feedback.md first, because hiding an upstream failure is explicitly not an acceptable fix.
  • REPORT — real bug, but not safe for an unattended fix: the correct behavior is ambiguous, it involves business logic or factual data, or it sits in heavily shared core code where the blast radius is unclear. List it in your final report so a human routes it to the right team.
  • FIX (in a dependency) — the trace lands in a package that is ours and that you have a clone of. sentry trace will show it as a vendor path (vendor/selectra/comparator-core/...), which looks like third-party noise and is not: check the path against the REPOS block before writing it off. Fix it in that repo's clone, on its own branch, against its own base — comparator-core is v3, not develop. Never edit a vendor/ path directly; it is a build artifact, and in these clones it is not even installed.
  • IGNORE: third-party — noise we can't and shouldn't fix: vendor-only stack traces with no in-app frames in a package we do not own, browser-extension/injected-script errors, crawler and probe junk (requests to /wp-admin, .php exploits, malformed URLs), CSP violations from external scripts.
  • IGNORE: infra — not code-fixable: DB/Redis connection failures or timeouts, DNS errors, upstream 502/503/504, out-of-memory, deploy-window blips. Exception: if the same infra error recurs heavily and code-level resilience would genuinely help, reclassify as FIX (or REPORT if too risky).

A vendor frame is where an error surfaced, not where it came from. Before attributing a root cause to a package — and above all before opening a register entry about the package — check whether our own repo explains it. Two cheap checks, either of which would have saved 29,000 events on TELECOM-COMPARATOR-NT: git log --follow the class or file in the trace, since a rename months ago still breaks anything that persisted the old name; and grep the repo for the failing symbol, because if nothing in app/ can produce it, the source is data and not code. Class … not found raised from a vendor recovery/queue path is almost always a stored row naming something we renamed, not a bug in the package that read the row. State which check you ran.

The same trap has a data-shaped version: an error that grows without plateauing usually means a failure that never clears its own trigger — a retried row that is never marked failed, a cache never invalidated. Find the loop before you name a culprit; "it grows" is evidence about the mechanism, not about whose fault it is.

When genuinely unsure between FIX and anything else, do not fix — classify as REPORT and explain the doubt. A wrong "fix" is worse than a week's delay.

Blast radius differs by repo, and so does the bar. cms is a monorepo serving dozens of sites in several languages from shared app/ and departments/ code: there, "safe for the site that errored" is not the bar, "safe for every site touching this code" is. comparator-core is a shared library and sits on the same side of that line — every comparator that depends on it inherits the change, and you can only see one of those consumers from here, so the bar is higher than for the repo that reported the error, not lower. telecom and renovation are single applications, so shared code there has one caller set you can actually read. Apply the caution the repo warrants — neither wave a shared-code change through, nor refuse a safe single-app fix because a rule about shared code says shared code is scary.

Step 3 — Fix

Cap the run at 3 fixes per repo and 6 in total, highest events × users first; journal the overflow as FIX (deferred) for the next run. Finish one repo before starting the next, so an interrupted run leaves whole repos done rather than all of them half done.

Three repos looks like something to parallelise. It is not: do every repo yourself, in this turn. Do not hand repos to background agents and wait for them — this run is headless, your turn ending is the run ending, and anything still in flight is killed. That is not hypothetical: it is how the first three-repo run spent 15 minutes and produced nothing at all. The caps above exist so one turn is enough.

For each repo you are fixing in:

  1. Read that repo's own CLAUDE.md before changing anything — its conventions, not the cms's, govern its code.
  2. Map Sentry server paths to the repo (/var/www/html/… → repo root) and read the actual code. Verify the bug still exists on the repo's base branch — it may already be fixed but not yet deployed. If so, record as FIX (already fixed, awaiting deploy) and move on.
  3. Branch countryos/global/<YYYY-MM-DD>-sentry-fixes from origin/<base>, where <base> is the base branch shown for that repo in the REPOS block. One commit per issue, message referencing the short id (e.g. fix: handle empty agency list on annuaire pages (CMS-42)).
  4. Fix the root cause, not the symptom — but stay minimal: handle the exact failing scenario from the event data; no refactors, no drive-by cleanups.
  5. Re-read the full diff before pushing: balanced Blade directives, no debug leftovers, behavior unchanged on the happy path, and no comment that restates the line under it (review-feedback.md — this is where that gets caught, and two of two PRs on 2026-08-31 shipped one). There is no PHP runtime in this container — the PR's CI is the executable check, your re-read is the local one.
  6. Push and open one draft PR per repo, targeting that repo's base branch: gh pr create --draft --base <base>, titled Sentry fixes <YYYY-MM-DD>. Body: one section per issue with the Sentry link, affected URLs, root cause in plain words, the fix, and how to verify after deploy. If nothing ended up fixable in a repo, open no PR for it.

Never put changes to two repos on one branch or in one PR. They are separate remotes with separate reviewers.

Step 4 — Register what a human has to own

A REPORT that exists only in your final message has not been reported. The Chat line scrolls away, and next week's gate — seeing the issue recorded in triage memory — will not raise it again. That is how a real defect goes quiet, and it is the exact failure decisions.md was built to fix one layer down.

So the worst of them go in the register: countries/_global/decisions.md, following the format of the entries already there (decisions list --country _global prints it).

The bar, and it is deliberately high: recurring, growing, and unowned. All three. A one-off spike is not recurring; a flat 200 events/week nobody has ever been hurt by is not growing; a bug in code this workspace can fix is not unowned — that is a FIX or a deferred FIX, not a question. At most 2 entries per run. A register with everything in it gets read exactly as often as a report with everything in it, which is the failure being fixed, reintroduced one level up. Everything below the bar stays in the final report, where it belongs.

Pick the type by who answers, not by how serious it is:

Type Use when Goes to
decision A trade-off nobody but a human may settle — an abandoned dependency to fork/replace/pin, a fix whose cost needs weighing Google Chat, answered by replying in the thread
report A real defect that someone else owns and can commit to — infrastructure, another team's service, a repo outside this workspace A standing GitHub issue. Set the Repo field, or it lands where nobody is looking

Write for the person answering, not for yourself: what is failing, how much and since when, what the options are, and what you recommend. Give the event count and the trend — "28k events, up 4x since last week" is what makes it urgent, and it is the one thing only you have looked up.

Name every Sentry shortId in the entry, each with its own count. An aggregate ("7 issues, ~28,500 events") cannot be checked by the person answering, and it hides the case where one shortId is 99% of the total. That is not hypothetical: OS-001 asked whether to replace an abandoned dependency and justified the urgency with 28.5k events, of which 28,403 belonged to a single issue the dependency was not causing — and it was answered on that basis. One root cause per entry. If the shortIds do not share a cause, they are two entries, and bundling them buys a decision made about the wrong thing.

Never mark an entry resolved, and never reopen one. Any task may open an entry; only a human closes it. If a past entry is now moot, say so in your report and leave the entry alone.

Step 5 — Record

Update memory/sentry-triage-<repo>.md for each repo, one line per issue:

| shortId | date | class | events at triage | affected sites | note / PR | fix ref |

The shortId and the event count are what the gate parses next week, so get those two columns right — a missing count means the issue can never be seen to have regressed, and a missing row means it comes back as NEW forever. The count must lead column 4; annotating after it is fine and useful (582 (was 241 on 2026-07-02)), but a count that does not come first is read wrong.

If the issue has a decisions.md entry, put its id in the class or note column (REPORT (OS-003)). The gate prints it on the worklist line, which is what stops a future run from re-diagnosing something a human is already holding — or, as happened here, from leaving a wrong diagnosis standing because nothing connected the row to the entry.

Every row you classify FIX must carry column 7, fix ref — exactly one of PR #18283, PR <owner>/<repo>#25 (a fix that landed in another repo), sha 4124f1ca5d, or none when nothing shipped that Sentry could observe. Write the PR number you actually opened this run; do not guess one.

That column is the whole loop. sentry verify uses it to find the commit, check whether it is in a build that is actually serving traffic, and then ask whether the errors stopped — after which sentry resolve closes the issue in Sentry, or next week's gate raises it as FIX-FAILED. A row without it is never resolved and never verified: the fix stays "unresolved" in Sentry forever, which is exactly the state this column was added to end. Prose in column 6 is not a substitute — it is free text and nothing parses it.

Append the usual dated entry to memory/journal.md. Final message: counts per class per repo, the PR URL for each repo that got one, any decisions.md entries you opened (by id), and the REPORT items that stayed below the register bar.