Use when investigating or fixing a Sentry issue - fetches the issue and its latest event, maps the stacktrace onto the current working tree, checks whether the failing code still exists, produces a root-cause analysis and a fix plan, and implements the fix only after explicit user approval. Triggers on a Sentry issue URL, a short id like PROJ-4F2, a numeric issue id, "what's breaking in production", triaging the top errors, and post-release regression checks.
Scanned 9/6/2026
Install to Claude Code
npx -y skills add muhammetsafak/SentryFixer.Skill --skill sentry-fixer --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of Sentry Fixer?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/muhammetsafak-sentry-fixer)More formats (shields.io, HTML) on the badges page.
---
name: sentry-fixer
description: Use when investigating or fixing a Sentry issue - fetches the issue and its latest event, maps the stacktrace onto the current working tree, checks whether the failing code still exists, produces a root-cause analysis and a fix plan, and implements the fix only after explicit user approval. Triggers on a Sentry issue URL, a short id like PROJ-4F2, a numeric issue id, "what's breaking in production", triaging the top errors, and post-release regression checks.
---
# sentry-fixer
Turns a Sentry issue into a fix: fetch it, map it onto the code actually in
this working tree, verify the code hasn't already moved on, reason about
root cause, propose a plan, and — only once the user says yes — implement
the fix and a regression test. Read-only against Sentry throughout, and
never touches git.
This file is the workflow spine. Domain detail (path normalization, the
stale-code method, root-cause reasoning, the fix-plan shape) lives in
`references/` and is loaded at the step that needs it — don't try to work
from memory of those files, read them at the point below that names them.
## 1. Preflight
Run `scripts/sentry.sh detect`. It prints `{"backend":"cli|api|none",
"org":..., "project":..., "base_url":..., "python":...}`.
`python` is the interpreter the script resolved by actually running it
(`python3`, then `python`, then `py`). **Use that value, not a literal
`python3`, for the `summarize_event.py` call in step 3** — on Windows the
`python3` on `PATH` is often the Microsoft Store stub, which is not Python.
- **Exit 3 (`backend":"none"`)**: no `sentry-cli` session and no token
found. Look for Sentry MCP tools already present in the session — if one
exists that fetches an issue, an event, and a list of issues, use those
in place of `sentry.sh` for the rest of this workflow (see
`references/sentry-access.md` → *MCP tool discovery* for how to match
them by purpose, since tool names aren't fixed). If no such tools are
present, tell the user the three ways to grant access — read
`references/sentry-access.md` → *Setting up access* and relay it — and
stop here.
- Confirm the working directory is a git checkout (e.g. `git rev-parse
--is-inside-work-tree`). Step 6's stale-code check depends on local
history; without a checkout, that step can only ever land on `unknown`.
- Take `org` / `project` from `detect`'s output.
- **`org` is required** for short-id resolution and every `list` call. If
it's empty and the mode needs it, ask the user.
- **`project` is optional everywhere.** No subcommand requires it: issue
and event detail are org-scoped, and `list` queries the whole
organization when no project is configured. In a multi-project org an
unset `project` is not an incomplete setup, it's the only correct one —
pinning a single project would silently answer "what's breaking in
production" for a fraction of production. **Never ask the user to pin
one**; note the value if it's there (step 4 uses it) and move on.
- Never infer org or project from the git remote name
(`references/sentry-access.md` explains why).
## 2. Mode
Infer which of four modes the user means from their phrasing; ask if it's
genuinely ambiguous. All three multi-issue modes end in the user picking
one issue and converging on the single-issue flow. **One issue is
analyzed and fixed per run** — a triage or regression sweep is for picking
*which* issue, not for fixing several in one pass.
| Mode | Trigger | Action |
|---|---|---|
| **Single issue** | A Sentry issue URL, a short id (`PROJ-4F2`), or a numeric issue id | Go straight to step 3 with that reference. |
| **Triage** | "what's breaking in production", "top errors", general sweep | `scripts/sentry.sh list --sort freq`. Rank candidates by `count × userCount`, weighted toward a recent `lastSeen`. Present a table, ask which to pursue, then proceed to step 3 with the chosen issue. |
| **Regression** | Post-release check, "anything new since the last release" | `scripts/sentry.sh list --query "firstRelease:<release>"` (ask for `<release>` if not given). Cross-check each candidate's `firstSeen` against the release date, present a table, ask which to pursue, then proceed to step 3 with the chosen issue. |
| **Path** | "errors in `src/payment`", a component or path named | Filter server-side — a `list` response carries no frames, so there is nothing client-side to match a path against: `scripts/sentry.sh list --query "is:unresolved stack.filename:\"**/src/payment/**\""`. `stack.filename`, `stack.module`, and `stack.function` are all documented issue-search properties; `culprit` matching can still narrow the returned set further. Present the matches, ask which to pursue, then proceed to step 3 with the chosen issue. |
**Table columns for all three multi-issue modes: `project`, `id`, `title`,
`count`, `users`, `lastSeen`.** `project` is not optional. `list` answers
across the whole organization unless a project is configured or `--project`
narrows it, so results routinely span services — and the same exception
class from two of them is otherwise two visually identical rows, with the
user picking blind. Take it from each row's `project.slug`, which the
organization issue index returns per issue. Short-id prefixes (`API-…`,
`WEB-…`) are a partial signal at best; don't rely on them instead.
`list` scope, if the user needs it narrowed or widened: `--project all` for
the whole org, `--project <slug>` or a comma-separated list for a subset
(slugs and numeric ids both work). With no flag, it uses the configured
project if there is one, and the whole org if there isn't.
## 3. Fetch
Fetch artifacts go to a scratch directory *outside* the working tree, not
into the repo — this workflow does not put anything in the working tree
before step 10:
```bash
work="$(mktemp -d)"
scripts/sentry.sh issue <ref> > "$work/issue.json"
scripts/sentry.sh event <id> > "$work/event.json" # defaults to the latest event; --oldest for the first occurrence
"$py" scripts/summarize_event.py --issue "$work/issue.json" < "$work/event.json" > "$work/summary.md" # $py is detect's "python" value
```
**Read `$work/summary.md`. Never read `$work/issue.json` or
`$work/event.json` directly** — a raw event is 50–300 KB and the signal
(in-app frames, recent breadcrumbs) drowns in SDK and vendor noise. The
summarizer's redaction only covers known credential/PII shapes, not
arbitrary free text, so treat even the summary as something not to paste
verbatim into a public place.
`$work` is the one place in this workflow where unredacted production data
touches disk. Delete it (`rm -rf "$work"`) when the run ends, whichever
step it ends at — step 10 does this on the path that reaches it, so do it
yourself if the run stops earlier, including when the user declines the
plan at step 9, and when step 4 stops the run.
## 4. Reconcile the project with this checkout
<STOP>
**Before mapping a single frame, establish that this issue belongs to the
project this working tree builds.** Everything after this step is scoped to
the current repo and cannot detect that it's looking at the wrong one.
The summary's header carries `- **Project:** <slug>`. Compare it to the
`project` value `detect` reported in step 1:
- **They match** → continue to step 5, no comment needed.
- **They differ, or the checkout resolves no project, or the summary has no
Project line** → **stop and ask.** State the issue's project, state what
this checkout resolves to (or that it resolves to nothing), and ask the
user to confirm that this working tree is that project's code. Continue
only on an explicit yes. Do not map frames, do not run the stale-code
check, and do not offer a hypothesis in the same message as the question.
Never settle this from the git remote name, the directory name, or a
package name — `references/sentry-access.md` explains why a remote doesn't
reliably map to a Sentry project, and a wrong answer here is invisible.
This gate exists because the failure it prevents is silent and looks
exactly like a successful run. With two services checked out side by side
and an issue belonging to the other one, a frame like
`/var/www/html/app/Exceptions/Handler.php` falls through every prefix
candidate to the basename search, which finds the single `Handler.php` in
*this* repo and reports an unambiguous match. Step 6 then runs the
stale-code check against the wrong git history and can answer `current`,
and step 7 reads that file and reasons about code that never ran. Every
stated guard is satisfied on the way; the playbook's ambiguity check is
scoped within the repo and cannot see the problem.
</STOP>
## 5. Map to code
For every in-app frame in the summary, normalize its reported path to a
working-tree file. Use the candidate order and basename-search fallback in
`references/analysis-playbook.md` → *Path normalization*, and record which
candidate resolved each frame. If a frame's file can't be located by any
candidate, say so explicitly in the analysis; do not skip it silently or
reason about the nearest-looking file as if it were confirmed.
## 6. Stale-code check
Do this **before** forming any root-cause hypothesis — a theory about code
that has since changed isn't a theory about the bug in front of you.
Follow `references/analysis-playbook.md` → *Stale-code check* exactly (SHA
extraction from the release string, `git cat-file`/`git diff`/`git log`
against the frame files, the rename caveat, and the `lastSeen`-based
fallback). Emit exactly one verdict: `current`, `changed`, or `unknown`.
## 7. Analyze
Reason about root cause per `references/analysis-playbook.md` → *Root-cause
method*: read the full source at each in-app frame located in step 5 (not
just Sentry's captured context lines) before forming any hypothesis,
ground the hypothesis in what that source actually does, walk the
breadcrumb trail, assess blast radius from the issue's counts and
seen-dates, search for sibling issues sharing the same `culprit`, and lay
out competing hypotheses side by side when the evidence doesn't settle on
one.
## 8. Present plan
Write the plan in the exact shape given by `references/plan-template.md`,
filling in every field — including the freshness caveat block up top when
step 6's verdict was `changed` or `unknown`. No file is touched yet.
## 9. Approval gate
<STOP>
**Do not create, modify, or delete a single file — anywhere in the working
tree — until the user has explicitly approved the plan presented in step
8.** This is unconditional: no confidence threshold, no "it's an obvious
fix", no exemption for a one-line change. If the user asks for changes to
the plan, or approves only part of it, go back to step 8, update the plan,
and present it again — do not proceed to step 10 on anything less than
explicit approval of the plan as presented.
</STOP>
## 10. Implement
Only after explicit approval:
- Apply exactly the changes from the approved plan.
- Add the regression test named in the plan.
- Run the project's test command (the one given under *Verification* in
the plan).
- Report the result honestly. If tests fail, show the actual failing
output — don't summarize it away or declare success anyway.
- Delete the scratch directory: `rm -rf "$work"`. `$work/event.json` is the
full unredacted production payload — cookies, `Authorization` headers,
user PII, everything `summarize_event.py` exists to strip. It is the only
place in this workflow where unredacted data touches disk, and nothing
else ever removes it.
Then **stop**. Do not commit, push, open a PR, or write anything back to
Sentry — that's the user's call, made outside this skill.
## Boundaries
- **Read-only against Sentry.** Every `sentry.sh` call (and any MCP
fallback used in its place) is a `GET`. Nothing in this workflow
resolves, assigns, mutes, or comments on a Sentry issue.
- **No git operations.** Reading history (`git cat-file`, `git diff`,
`git log`) is fine and required for step 6; committing, branching, or
pushing is not part of this skill at any step.
- **The project gate in step 4 is unconditional.** No frame is mapped, and
no root-cause reasoning starts, until the issue's project is reconciled
with this checkout or the user confirms it explicitly.
- **The approval gate in step 9 is unconditional.** Nothing in steps 1–8
ever justifies skipping it.
## Troubleshooting
`scripts/sentry.sh` exit codes and what to do about each:
| Exit | Meaning | What to do |
|---|---|---|
| `0` | OK | Continue. |
| `3` | No backend available (`detect`), or no token found when a data command needed one | Look for Sentry MCP tools in the session; otherwise point the user at `references/sentry-access.md` → *Setting up access* and stop. |
| `4` | No usable Python 3.8+ interpreter — none of `python3`, `python`, `py` runs and reports 3.8+ | Tell the user Python 3.8+ is required (and that on Windows the `python3` on `PATH` is often the Microsoft Store stub, not Python — `SENTRY_PYTHON` can name a working one), then stop; nothing in this workflow can proceed without it. |
| `5` | The request failed — `curl` itself failed, or the response was non-2xx | Surface the stderr message as-is; don't retry silently. For a non-2xx, Sentry's own response body is printed above it — read that first: it names the missing scope on a 403 and the bad parameter on a 400. |
| `6` | The reference resolved to no issue id (including an unknown short id, whose lookup 404s) | Confirm the id, short id, or URL with the user; a typo or the wrong org is the common cause. Sentry's 404 body is printed above the message. |
| `64` | Usage or validation error (bad/missing flag, unresolved org/project, an unparseable reference, or a rejected character) | The stderr message names exactly what was wrong — fix that input; don't work around the script. |
| `130` | Interrupted (SIGINT) | Stop; the call was cancelled. |
| `143` | Terminated (SIGTERM) | Stop; the process was terminated externally. |
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!