Sentry Error Tracking skill for Claude Code
Readable stack traces, quiet quotas, useful alerts. Guides Claude through a Sentry setup that actually helps on-call: every event tagged with release and environment, source maps uploaded from CI with the auth token kept secret, errors kept at full rate while traces and replays are sampled, and noise filtered at the SDK before it burns quota. It finishes with alert rules for new and regressed issues and a weekly triage routine.
- name
sentry-error-tracking - size247 lines · ≈2,450 tokens
- files1
- licenseMIT
- updated
What Claude sees before loading it
This description is the only part of the skill that sits in context all the time. Claude reads it to decide when to load the full playbook below.
description: Sets up and tunes Sentry for web, Node, and Python apps - SDK init per framework, source map upload with debug IDs, releases and commit tracking, environment tagging, error and trace sampling, PII scrubbing, noise filtering, fingerprinting, alert rules, and an issue triage routine. Use when installing Sentry, stack traces show minified code, the quota is burning on noise, alerts are too loud or silent, or when deciding sample rates for tracing and session replay. Try asking
- Set up Sentry in our Next.js app with source maps uploaded from GitHub Actions
- Sentry stack traces are still minified even though we upload source maps - why?
- We blew through our Sentry quota in three days; cut the noise without losing real errors
Use it when
- Installing or upgrading a Sentry SDK in web, Node, or Python code
- Stack traces show minified or unmatched frames
- Quota is consumed by extension errors, bots, or one noisy issue
- Alerts are either ignored because of noise or missing real regressions
Skip it when
- You need vendor-neutral tracing and metrics pipelines - use opentelemetry instead
- You are digging through raw server logs for a cause - use log-analysis instead
How to check the result
Throw a deliberate test error per runtime in staging and confirm it arrives with the correct release, environment, readable source frames and no auth headers or cookies.
The common mistake it prevents
The release string used for source map upload differs from the one set in Sentry.init, so maps never match and traces stay minified.
Sentry is set up well when every event carries a release, environment, and readable stack trace pointing at your source; the quota is spent on real defects rather than browser-extension noise; no secrets or PII leave the app; and alerts page someone only for new or regressed issues that affect users.
Ground rules
- The DSN is not a secret (it ships to browsers), but the auth token used for source map
upload and release creation is. Keep
SENTRY_AUTH_TOKENin CI secrets only, never in code or.envfiles committed to git. - Set
releaseandenvironmenton every SDK init. Without a release you lose regression detection, suspect commits, and source map matching. - Errors: keep
sampleRateat 1.0 and filter noise instead. Traces, replays, and profiles: sample. - Default
sendDefaultPiito false. Add user context deliberately (id, maybeemailif the privacy policy allows), not by turning on blanket PII. - Use the official wizard or framework SDK; avoid hand-wiring
@sentry/browserinto a framework that has its own SDK (Next.js, SvelteKit, Remix/React Router, Nuxt, Django).
Workflow
- [ ] 1. Inventory runtimes and current SDK versions
- [ ] 2. Install/upgrade SDK per runtime
- [ ] 3. Configure release, environment, sampling, scrubbing
- [ ] 4. Source maps / debug files uploaded in CI
- [ ] 5. Filter noise and set fingerprints
- [ ] 6. Alert rules and ownership
- [ ] 7. Verify with a deliberate test error per runtime
1. Inventory
grep -rn "@sentry/" package.json */package.json 2>/dev/null
grep -rn "sentry-sdk" requirements*.txt pyproject.toml 2>/dev/null
grep -rn "Sentry.init\|sentry_sdk.init" --include=*.{ts,tsx,js,mjs,py} . | grep -v node_modules
Check all @sentry/* packages share one version; mixed versions cause runtime errors.
2. Install
| Runtime | Install |
|---|---|
| Next.js | npx @sentry/wizard@latest -i nextjs (creates instrumentation.ts, instrumentation-client.ts, server/edge configs, wraps next.config) |
| React/Vite SPA | npm i @sentry/react @sentry/vite-plugin |
| Node/Express | npm i @sentry/node; init in a file loaded first (node --import ./instrument.mjs app.mjs) |
| SvelteKit, Nuxt, Remix/React Router, Astro | npx @sentry/wizard@latest -i <framework> or that framework’s @sentry/* package |
| Python (Django, Flask, FastAPI) | pip install "sentry-sdk"; integrations auto-enable when the framework is detected |
| React Native / Expo | npx @sentry/wizard@latest -i reactNative |
3. Configure
Browser (React):
import * as Sentry from "@sentry/react";
Sentry.init({
dsn: import.meta.env.VITE_SENTRY_DSN,
release: import.meta.env.VITE_RELEASE, // same string CI uses for upload
environment: import.meta.env.MODE,
integrations: [Sentry.browserTracingIntegration(), Sentry.replayIntegration()],
tracesSampleRate: 0.1,
tracePropagationTargets: [/^\/api\//, /^https:\/\/api\.example\.com/],
replaysSessionSampleRate: 0.0,
replaysOnErrorSampleRate: 1.0,
sendDefaultPii: false,
ignoreErrors: ["ResizeObserver loop completed with undelivered notifications"],
denyUrls: [/extensions\//i, /^chrome-extension:\/\//i, /^moz-extension:\/\//i],
});
Node (load before anything else):
// instrument.mjs
import * as Sentry from "@sentry/node";
Sentry.init({
dsn: process.env.SENTRY_DSN,
release: process.env.RELEASE,
environment: process.env.APP_ENV ?? "development",
tracesSampler: ({ name, inheritOrSampleWith }) =>
name.includes("/healthz") ? 0 : inheritOrSampleWith(0.1),
sendDefaultPii: false,
});
Python:
import os, sentry_sdk
sentry_sdk.init(
dsn=os.environ.get("SENTRY_DSN"),
release=os.environ.get("RELEASE"),
environment=os.environ.get("APP_ENV", "development"),
traces_sample_rate=0.1,
send_default_pii=False,
)
Sampling defaults:
| Signal | Start at | Raise when |
|---|---|---|
Errors (sampleRate) | 1.0 | Never lower; filter noise instead |
| Traces | 0.05-0.2 in prod, 1.0 in staging | Low-traffic services: up to 1.0 |
| Replay sessions | 0-0.05 | Investigating a UX issue, temporarily |
| Replay on error | 0.5-1.0 | Default high; it is the most useful replay |
| Health checks, bots, static assets | 0 via tracesSampler | - |
Scrubbing: use beforeSend (and beforeSendTransaction) to drop or redact fields your
app knows are sensitive; server-side Data Scrubbing in project settings is a second layer,
not the only one. Mask all text and inputs in Replay unless a page is explicitly safe.
beforeSend(event) {
if (event.request?.headers) delete event.request.headers["authorization"];
if (event.request?.cookies) event.request.cookies = {};
return event;
}
Context to add: Sentry.setUser({ id }) after login (clear on logout with
Sentry.setUser(null)), Sentry.setTag("tenant", id) for filterable dimensions (low
cardinality), Sentry.setContext("order", {...}) for detail.
4. Source maps and releases in CI
Prefer the bundler plugin; it injects debug IDs and uploads in one step:
// vite.config.ts
import { sentryVitePlugin } from "@sentry/vite-plugin";
export default defineConfig({
build: { sourcemap: true },
plugins: [
react(),
sentryVitePlugin({
org: process.env.SENTRY_ORG,
project: process.env.SENTRY_PROJECT,
authToken: process.env.SENTRY_AUTH_TOKEN,
release: { name: process.env.RELEASE },
sourcemaps: { filesToDeleteAfterUpload: ["./dist/**/*.map"] },
}),
],
});
Without a plugin, use sentry-cli:
export SENTRY_AUTH_TOKEN SENTRY_ORG SENTRY_PROJECT # from CI secrets
VERSION="$(git rev-parse --short HEAD)"
sentry-cli releases new "$VERSION"
sentry-cli releases set-commits "$VERSION" --auto
sentry-cli sourcemaps inject ./dist
sentry-cli sourcemaps upload --release="$VERSION" ./dist
sentry-cli releases finalize "$VERSION"
- The same
VERSIONmust reach the runtimereleaseoption. Derive both from the commit SHA in CI. - Do not serve
.mapfiles publicly unless you intend to; delete after upload or block them at the CDN. - Record deploys per environment (the CLI has a deploys command; check
sentry-cli --helpfor the syntax of the installed version) so “Resolved in next release” and release health work. set-commits --autoneeds the GitHub/GitLab integration installed and the repo linked.
5. Noise control and grouping
Fix noise at the source in this order:
denyUrls/allowUrlsfor third-party scripts and extensions.ignoreErrorsfor known-benign messages (ResizeObserver, cancelled fetches on navigation,Non-Error promise rejection capturedfrom libraries you cannot fix).- Inbound filters in project settings (legacy browsers, web crawlers, localhost).
- Rate limits / spike protection per key so one bad deploy cannot drain the quota.
Grouping:
- Wrapped or rethrown errors often group too broadly or too narrowly. Set fingerprints for
known cases:
scope.setFingerprint(["payment-provider-timeout", provider]). - Prefer server-side fingerprint and stack-trace rules (project settings) for patterns, so changes apply without deploys.
- Throw
Errorobjects, never strings or plain objects; capture withSentry.captureException(err)and add context instead of formatting it into the message (dynamic messages fragment grouping).
6. Alerts and ownership
| Alert | Rule | Route to |
|---|---|---|
| New issue in production | First seen, environment=production, level >= error | Team channel |
| Regression | Issue changes from resolved to unresolved | Owner of the code (ownership rules) |
| Spike | Issue seen > N times in 10 min, or affects > N users | On-call pager |
| Release health | Crash-free sessions/users drops below target (e.g. 99.5%) | Release owner |
| Performance | p95 of key transaction above threshold | Owning team, not pager |
- Configure ownership rules or CODEOWNERS import so issues auto-assign.
- Alert on users affected, not raw event count, for client apps.
- Use Crons monitors for scheduled jobs (missed or failed check-ins), not error alerts.
Triage routine
Weekly, sort unresolved production issues by users affected:
- Real bug with clear owner -> assign, link ticket, set priority.
- Expected/benign -> fix the code to not throw, or filter it; do not just ignore in UI.
- Fixed by a deploy -> “Resolve in next release” so regressions reopen it.
- Third-party noise -> add to
denyUrls/inbound filters, then delete the issue.
Gotchas
- Next.js: client init belongs in
instrumentation-client.ts(current versions); server and edge init are imported fromregister()ininstrumentation.ts, andonRequestErrorshould be exported to capture server component errors. - Ad blockers drop browser events; use the Next.js
tunnelRouteoption or a customtunnelendpoint when data loss matters. - Initializing Node Sentry after importing the app breaks auto-instrumentation; it must
load first (
--import). - Source maps uploaded for release A do not apply to events from release B; mismatched release strings are the usual cause of minified traces. Debug IDs remove most of this pain, so prefer plugin-based upload.
- Tags with high cardinality (user IDs, URLs with IDs) degrade search; use context or user fields.
Verification
- Trigger a test error per runtime (a temporary route throwing
new Error("sentry test")) in staging. Confirm it arrives with the right release, environment, readable source frames, and suspect commits. - Confirm no
Authorizationheader, cookies, or tokens appear in the event JSON. - Check the stats page after a day: dropped vs accepted, top noisy issues.
- Fire each alert rule once (or use rule preview) and confirm it reaches the right destination. Remove the test route afterwards.
About this skill
Written and maintained by the Claude Skills editors to the same editorial standard as every skill in the directory: a selective trigger, a real workflow, known pitfalls and a verification step. First published , last updated . MIT licensed.