/sentry — error monitoring (multi-org)
REST against https://sentry.io/api/0/ (or self-hosted Sentry instance).
Bearer auth with API token. Profiles isolate org/project pairs.
Arguments: $ARGUMENTS. Profile resolution: --profile <name> → SENTRY_PROFILE → ~/.sentry/active_profile → [default].
Overview
REST against https://sentry.io/api/0/ (or self-hosted). Bearer auth with API token. Profiles isolate org/project pairs. Default summary view (no PII) — opt-in --full for raw stacktraces.
When to Use
- Investigating issues during incidents (latest unresolved, last 24h)
- Resolving or assigning issues to teammates
- Release health (new issues per release, commit count)
- Self-hosted Sentry instances (
api_urlper profile) - Triaging error spikes across multiple projects in one org
When NOT to Use
- Sending events to Sentry → use the SDK in your app, not this skill
- Bulk operations on hundreds of issues → use Sentry's UI bulk actions
- Sourcemap upload → use
@sentry/clior Webpack/Vite plugin - User management at scale → Sentry's admin UI
Dependencies
curl, jq.
Profile config
~/.sentry/credentials (mode 600):
[default]
api_url = https://sentry.io/api/0
auth_token = sntrys_xxxxxxxxxxxxxxxxxxxxxxxx
org = my-org-slug
project = backend
[work]
api_url = https://sentry.example.com/api/0 # self-hosted
auth_token = sntrys_xxxxxxxxxxxxxxxxxxxxxxxx
org = company-org
project = api
[client_a]
api_url = https://sentry.io/api/0
auth_token = sntrys_xxxxxxxxxxxxxxxxxxxxxxxx
org = client-a-org
project = mobile
Token scopes (least privilege):
| Operation | Required scope |
|---|---|
| Read issues, events | event:read + project:read |
| Resolve / assign issues | event:write + project:read |
| Read releases | project:releases |
| Manage members | org:read (avoid unless needed) |
Get token: User Settings → Auth Tokens → Create New Token. Use org-scoped tokens (limits to one org) when possible.
Helpers
Shared profile/INI/
ctt_*pattern reference: profiles-and-credentials.
source "$HOME/.claude-team-toolkit/lib/credentials.sh"
ctt_load_creds sentry "$PROFILE"
sentry_api() {
local method="$1" path="$2"; shift 2
curl -s -X "$method" \
-H "Authorization: Bearer $CTT_AUTH_TOKEN" \
-H "Content-Type: application/json" \
"$@" \
"$CTT_API_URL$path"
}
Dispatch
issues [--query <q>] [--limit N] — list issues
Q="${QUERY:-is:unresolved}"
sentry_api GET "/projects/$CTT_ORG/$CTT_PROJECT/issues/?query=$(printf %s "$Q" | jq -sRr @uri)&limit=${LIMIT:-25}" \
| jq -r '.[] | "\(.shortId)\t\(.level)\t\(.count)\t\(.title)\n \(.permalink)"'
Common queries:
is:unresolved— open issuesis:unresolved age:-24h— last 24hlevel:error— errors onlyrelease:1.2.3— specific releaseassigned:me— yours
issue <issueId> — full issue detail
sentry_api GET "/issues/$1/" | jq '{
id: .shortId, title, level, status, count, userCount,
firstSeen, lastSeen, assignedTo,
release: .firstRelease.version,
permalink
}'
events <issueId> [--limit N] — recent events for an issue
sentry_api GET "/issues/$1/events/?limit=${LIMIT:-10}" \
| jq -r '.[] | "\(.eventID)\t\(.dateCreated)\t\(.user.email // "—")"'
event <eventId> — full event detail (stacktrace + context)
sentry_api GET "/projects/$CTT_ORG/$CTT_PROJECT/events/$1/" | jq '{
id: .eventID, message, level, dateCreated,
user, environment, release, dist,
exception: (.entries[] | select(.type=="exception") | .data.values[0] | {type, value, frames: (.stacktrace.frames | map({function, filename, lineno}))})
}'
resolve <issueId> — mark resolved
source "$HOME/.claude-team-toolkit/lib/confirm.sh"
ctt_confirm "Resolve issue $1 on $CTT_PROFILE?" || return 1
sentry_api PUT "/issues/$1/" -d '{"status":"resolved"}'
ctt_audit_log sentry "resolved $1"
assign <issueId> <username>
BODY=$(jq -n --arg u "$2" '{assignedTo: $u}')
sentry_api PUT "/issues/$1/" -d "$BODY"
ctt_audit_log sentry "assigned $1 → $2"
releases [--limit N] — recent releases
sentry_api GET "/organizations/$CTT_ORG/releases/?per_page=${LIMIT:-20}" \
| jq -r '.[] | "\(.version)\t\(.dateCreated)\t\(.newGroups // 0) new issues\t\(.commitCount) commits"'
release <version> — release detail + health
sentry_api GET "/organizations/$CTT_ORG/releases/$1/" | jq '{
version, dateCreated,
newGroups, commitCount,
authors: [.authors[].name],
projects: [.projects[].slug]
}'
projects — list projects in org
sentry_api GET "/organizations/$CTT_ORG/projects/" \
| jq -r '.[] | "\(.slug)\t\(.platform)\t\(.id)"'
Safety
- Issue/event content (stacktraces, user data, error messages) often contains
PII — never paste raw output into public chats. The skill should
default to summary view;
--fullflag opt-in for raw. assignedTofield can be a user OR a team — confirm with user which.- Resolve is reversible (status:unresolved) but
deleteis not — there is intentionally NOdeletecommand in this skill. - Self-hosted Sentry:
api_urlincludes path/api/0. Don't forget. - 429 Too Many Requests: Sentry API rate limit (40 req/s default for free).
Common Mistakes
- Self-hosted: forgetting
/api/0inapi_url→ 404 on every call - Pasting raw event output publicly → leaks PII (user IDs, request bodies)
- Using user-scoped tokens → blast radius is all your orgs. Use org-scoped.
- Resolving without root cause → issue re-opens on next event, frustration loop
- 429 ignored → API gets temp-banned. Respect
Retry-After. - Querying with
is:resolvedthen surprised it includes old issues — addage:-Ndfor recency
Token-saving tip
Use organization-scoped tokens. They're limited to one org which limits blast radius if leaked.