Generated banner for Sentry
Generated logo for Sentry

Sentry

JetBrainsOfficialCreated by OpenAIDistributed by JetBrains

  • Skill
  • Tool integration
  • Cloud and DevOps
  • Apache-2.0
  • Low risk
  • Has scripts

From the author

Use when the user asks to inspect Sentry issues or events, summarize recent production errors, or pull basic Sentry health data via the Sentry API; perform read-only queries with the bundled script and require `SENTRY_AUTH_TOKEN`.
— the description by OpenAI

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

The Sentry skill provides a structured way to interact with Sentry observability data directly from your environment. By utilizing a bundled Python script, it enables read-only access to production error tracking, allowing users to list unresolved issues, retrieve specific issue details, and inspect individual event data. The skill emphasizes secure practices by requiring an environment-based authentication token and ensuring PII redaction in all outputs.

What it does

  • Lists recent unresolved issues from Sentry projects.
  • Resolves issue short IDs to internal system IDs.
  • Retrieves detailed information for specific Sentry issues.
  • Fetches event logs associated with a particular issue.
  • Provides event-specific metadata including release and environment details.
  • Handles API pagination and retries for robust data retrieval.

When to use it

  • When you need to summarize recent production errors without leaving your terminal.
  • When investigating specific issue reports identified by their short ID.
  • When auditing the health of a production environment via Sentry data.
  • When you need to pull event details for debugging purposes.

Advantages

  • Provides direct, deterministic access to Sentry data via API.
  • Maintains security by using read-only scopes and environment variables.
  • Automates pagination and error handling for consistent results.
  • Redacts sensitive information like PII from output logs.
  • Supports filtering by environment, time range, and custom queries.

Usage examples

“List the top 10 open issues for prod in the last 24h.”
The agent executes a query to fetch the 10 most recent unresolved issues, displaying their titles, short IDs, counts, and last seen timestamps.
“Show me the details for issue ABC-123.”
The agent resolves the short ID to an internal ID and retrieves the full issue details, including status and occurrence metadata.

Install

  1. Download the skill as a ZIP:

    https://mp-staging.autogineer.com/agent-skills/jetbrains/sentry/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip sentry.zip -d .agents/skills

Documentation by OpenAI

Sentry (Read-only Observability)

Quick start

  • If not already authenticated, ask the user to provide a valid SENTRY_AUTH_TOKEN (read-only scopes such as project:read, event:read) or to log in and create one before running commands.

  • Set SENTRY_AUTH_TOKEN as an env var.

  • Optional defaults: SENTRY_ORG, SENTRY_PROJECT, SENTRY_BASE_URL.

  • Defaults: org/project {your-org}/{your-project}, time range 24h, environment prod, limit 20 (max 50).

  • Always call the Sentry API (no heuristics, no caching).

If the token is missing, give the user these steps:

  1. Create a Sentry auth token: https://sentry.io/settings/account/api/auth-tokens/

  2. Create a token with read-only scopes such as project:read, event:read, and org:read.

  3. Set SENTRY_AUTH_TOKEN as an environment variable in their system.

  4. Offer to guide them through setting the environment variable for their OS/shell if needed.

  • Never ask the user to paste the full token in chat. Ask them to set it locally and confirm when ready.

Core tasks (use bundled script)

Use scripts/sentry_api.py for deterministic API calls. It handles pagination and retries once on transient errors.

Skill path (set once)

export CODEX_HOME="${CODEX_HOME:-$HOME/.codex}"
export SENTRY_API="$CODEX_HOME/skills/sentry/scripts/sentry_api.py"

User-scoped skills install under $CODEX_HOME/skills (default: ~/.codex/skills).

1) List issues (ordered by most recent)
python3 "$SENTRY_API" \
  list-issues \
  --org {your-org} \
  --project {your-project} \
  --environment prod \
  --time-range 24h \
  --limit 20 \
  --query "is:unresolved"
2) Resolve an issue short ID to issue ID
python3 "$SENTRY_API" \
  list-issues \
  --org {your-org} \
  --project {your-project} \
  --query "ABC-123" \
  --limit 1

Use the returned id for issue detail or events.

3) Issue detail
python3 "$SENTRY_API" \
  issue-detail \
  1234567890
4) Issue events
python3 "$SENTRY_API" \
  issue-events \
  1234567890 \
  --limit 20
5) Event detail (no stack traces by default)
python3 "$SENTRY_API" \
  event-detail \
  --org {your-org} \
  --project {your-project} \
  abcdef1234567890

API requirements

Always use these endpoints (GET only):

  • List issues: /api/0/projects/{org_slug}/{project_slug}/issues/

  • Issue detail: /api/0/issues/{issue_id}/

  • Events for issue: /api/0/issues/{issue_id}/events/

  • Event detail: /api/0/projects/{org_slug}/{project_slug}/events/{event_id}/

Inputs and defaults

  • org_slug, project_slug: default to {your-org}/{your-project} (avoid non-prod orgs).

  • time_range: default 24h (pass as statsPeriod).

  • environment: default prod.

  • limit: default 20, max 50 (paginate until limit reached).

  • search_query: optional query parameter.

  • issue_short_id: resolve via list-issues query first.

Output formatting rules

  • Issue list: show title, short_id, status, first_seen, last_seen, count, environments, top_tags; order by most recent.

  • Event detail: include culprit, timestamp, environment, release, url.

  • If no results, state explicitly.

  • Redact PII in output (emails, IPs). Do not print raw stack traces.

  • Never echo auth tokens.

Golden test inputs

  • Org: {your-org}

  • Project: {your-project}

  • Issue short ID: {ABC-123}

Example prompt: “List the top 10 open issues for prod in the last 24h.” Expected: ordered list with titles, short IDs, counts, last seen.

Files

scripts/sentry_api.py

#!/usr/bin/env python3
import argparse
import json
import os
import re
import sys
import time
from urllib.error import HTTPError, URLError
from urllib.parse import urlencode
from urllib.request import Request, urlopen

DEFAULT_BASE_URL = "https://sentry.io"
DEFAULT_ORG = "your-org"
DEFAULT_PROJECT = "your-project"
MAX_LIMIT = 50

EMAIL_RE = re.compile(r"[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Za-z]{2,}")
IP_RE = re.compile(r"\b(?:\d{1,3}\.){3}\d{1,3}\b")


def redact_string(value):
    value = EMAIL_RE.sub("[REDACTED_EMAIL]", value)
    value = IP_RE.sub("[REDACTED_IP]", value)
    return value


def redact_data(value):
    if isinstance(value, str):
        return redact_string(value)
    if isinstance(value, list):
        return [redact_data(item) for item in value]
    if isinstance(value, dict):
        redacted = {}
        for key, item in value.items():
            if key.lower() in {"email", "ip", "ip_address"}:
                redacted[key] = "[REDACTED]"
            else:
                redacted[key] = redact_data(item)
        return redacted
    return value


def next_cursor(link_header):
    if not link_header:
        return None
    for part in link_header.split(","):
        if 'rel="next"' in part and 'results="true"' in part:
            match = re.search(r'cursor="([^"]+)"', part)
            if match:
                return match.group(1)
    return None


def request_json(url, token, retries=1):
    req = Request(url)
    req.add_header("Authorization", f"Bearer {token}")
    req.add_header("Accept", "application/json")

    attempt = 0
    while attempt <= retries:
        try:
            with urlopen(req) as resp:
                body = resp.read().decode("utf-8")
                data = json.loads(body) if body else None
                return data, resp.headers
        except HTTPError as err:
            body = err.read().decode("utf-8", "ignore")
            if attempt < retries and (err.code >= 500 or err.code == 429):
                attempt += 1
                time.sleep(1)
                continue
            raise RuntimeError(f"HTTP {err.code} for {url}: {body or 'request failed'}") from err
        except URLError as err:
            if attempt < retries:
                attempt += 1
                time.sleep(1)
                continue
            raise RuntimeError(f"Network error for {url}: {err.reason}") from err
    raise RuntimeError(f"Request retries exhausted for {url}")


def build_url(base_url, path, params=None):
    base = base_url.rstrip("/")
    url = f"{base}{path}"
    if params:
        url = f"{url}?{urlencode(params, doseq=True)}"
    return url


def paged_get(base_url, path, params, token, limit):
    results = []
    cursor = None
    while len(results) < limit:
        page_params = dict(params)
        page_params["per_page"] = min(MAX_LIMIT, limit - len(results))
        if cursor:
            page_params["cursor"] = cursor
        url = build_url(base_url, path, page_params)
        data, headers = request_json(url, token)
        if not data:
            break
        results.extend(data)
        cursor = next_cursor(headers.get("Link"))
        if not cursor:
            break
    return results[:limit]


def require_org_project(org, project):
    if org == DEFAULT_ORG or project == DEFAULT_PROJECT:
        raise RuntimeError(
            "Missing org/project. Set SENTRY_ORG and SENTRY_PROJECT or pass --org/--project."
        )


def handle_list_issues(args, token, base_url):
    require_org_project(args.org, args.project)
    limit = min(args.limit, MAX_LIMIT)
    params = {
        "statsPeriod": args.time_range,
        "environment": args.environment,
    }
    if args.query:
        params["query"] = args.query

    path = f"/api/0/projects/{args.org}/{args.project}/issues/"
    issues = paged_get(base_url, path, params, token, limit)
    return issues


def handle_issue_detail(args, token, base_url):
    path = f"/api/0/issues/{args.issue_id}/"
    url = build_url(base_url, path)
    data, _ = request_json(url, token)
    return data


def handle_issue_events(args, token, base_url):
    limit = min(args.limit, MAX_LIMIT)
    path = f"/api/0/issues/{args.issue_id}/events/"
    events = paged_get(base_url, path, {}, token, limit)
    return events


def handle_event_detail(args, token, base_url):
    require_org_project(args.org, args.project)
    path = f"/api/0/projects/{args.org}/{args.project}/events/{args.event_id}/"
    url = build_url(base_url, path)
    data, _ = request_json(url, token)
    if data and not args.include_entries:
        data = dict(data)
        data.pop("entries", None)
    return data


def build_parser():
    parser = argparse.ArgumentParser(
        description="Read-only Sentry API helper for issues and events"
    )
    parser.add_argument(
        "--base-url",
        default=os.environ.get("SENTRY_BASE_URL", DEFAULT_BASE_URL),
        help="Sentry base URL (default: https://sentry.io)",
    )
    parser.add_argument(
        "--org",
        default=os.environ.get("SENTRY_ORG", DEFAULT_ORG),
        help="Sentry org slug",
    )
    parser.add_argument(
        "--project",
        default=os.environ.get("SENTRY_PROJECT", DEFAULT_PROJECT),
        help="Sentry project slug",
    )
    parser.add_argument(
        "--no-redact",
        action="store_true",
        help="Do not redact PII in output",
    )

    subparsers = parser.add_subparsers(dest="command", required=True)

    list_issues = subparsers.add_parser("list-issues", help="List issues")
    list_issues.add_argument("--time-range", default="24h")
    list_issues.add_argument("--environment", default="prod")
    list_issues.add_argument("--query", default="")
    list_issues.add_argument("--limit", type=int, default=20)

    issue_detail = subparsers.add_parser("issue-detail", help="Issue detail")
    issue_detail.add_argument("issue_id")

    issue_events = subparsers.add_parser("issue-events", help="Issue events")
    issue_events.add_argument("issue_id")
    issue_events.add_argument("--limit", type=int, default=20)

    event_detail = subparsers.add_parser("event-detail", help="Event detail")
    event_detail.add_argument("event_id")
    event_detail.add_argument(
        "--include-entries",
        action="store_true",
        help="Include event entries (may contain stack traces)",
    )

    return parser


def main():
    parser = build_parser()
    args = parser.parse_args()

    token = os.environ.get("SENTRY_AUTH_TOKEN")
    if not token:
        raise RuntimeError("Missing SENTRY_AUTH_TOKEN env var.")

    base_url = args.base_url

    if args.command == "list-issues":
        data = handle_list_issues(args, token, base_url)
    elif args.command == "issue-detail":
        data = handle_issue_detail(args, token, base_url)
    elif args.command == "issue-events":
        data = handle_issue_events(args, token, base_url)
    elif args.command == "event-detail":
        data = handle_event_detail(args, token, base_url)
    else:
        raise RuntimeError(f"Unknown command: {args.command}")

    if not args.no_redact:
        data = redact_data(data)

    print(json.dumps(data, indent=2, sort_keys=True))


if __name__ == "__main__":
    try:
        main()
    except RuntimeError as exc:
        print(f"Error: {exc}", file=sys.stderr)
        sys.exit(1)

Requirements and compatibility

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Needs Python

Security

What our scanner noticed· Low risk

  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
OpenAI
Publisher
JetBrains (Official)
Distributed by
JetBrains
Source
JetBrains/skills · sentry · commit 3f53782
Licence
Apache License 2.0 (redistribution allowed)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by OpenAI or JetBrains.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download sentry.zip