Aligns Claude's plugin.json with Cursor, Codex, and Grok which all
include ?utm_source=plugin in their distributed MCP configs.
Co-Authored-By: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Documents the two telemetry surfaces for sentry-for-ai:
- Primary: sentry/mcp-server spans filtered by app.utm_source:plugin
(Cursor, Codex, Grok attributed; Claude gap noted)
- Secondary: installer CLI errors in project 4511570959335425
Includes query recipes, investigation pivots, domains, configuration,
and attribute notes following the sentry-mcp/junior TELEMETRY.md pattern.
Co-Authored-By: Junior (Sentry AI assistant)
Co-Authored-By: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
The preReleaseCommand failed in craft's container with "pnpm: command not
found" -- the image ships npm but not pnpm. Switch to `npm pkg set version`,
a plain package.json edit with no install, lockfile, or git-check side
effects.
Add a tagline under the banner and rework the closing line so the run
opens and closes on the same beat ("teaching your agents to speak
Sentry" -> "your agents now speak Sentry") instead of a flat restart
hint.
On a color terminal the tagline animates: a bright band shimmers across
"speak Sentry" while the install runs, then settles to a static line.
It is painted as a header by a custom listr renderer (ShimmerRenderer)
so the step list renders normally beneath it; off a TTY (CI, pipes,
NO_COLOR) it stays a plain static line. The shimmer effect lives in its
own module, text-shimmer.ts.
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
craft's default version bump targets the repo-root package.json, which is
private and unversioned. The published package lives in packages/installer,
so its version stayed at 0.1.0 and npm rejected the 0.1.1 publish as a
duplicate. Wire a preReleaseCommand that bumps packages/installer instead.
When an install streams through an output sink, runStreaming piped the
command's stderr to the live view but never captured it, so a failure
fell through to runInstallCommand's generic "exit code N" message. The
interactive UI always passes a sink, so Listr failure text was almost
always that generic message instead of the agent's actual error.
Tee stderr: keep piping it to the sink for the live view, and also
buffer it into the result so the thrown error carries the real text.
Show what each agent is actually doing instead of a static spinner. An
optional output sink threads from the install task down through
install/update/cleanup to the shell runner; the real system spawns the
command and pipes its stdout/stderr into the task's stream, so progress
appears live under each agent's row. Detection probes still buffer and
parse as before — only the mutating commands stream.
Theme the renderer to match the Sentry banner: diamond task icons with a
small-to-large pulsing diamond spinner, streamed output grayed (per line,
so blank lines stay empty and removeEmptyLines can drop them) and kept on
screen via persistentOutput, an arrow prompt cursor with the gap moved
onto the checkbox icons to preserve alignment, and a blue hollow-diamond
prompt prefix. Also drop the Cursor restart note now that the closing
line covers it.
The install subtasks run under a parent "Installing plugins" task, which
collapses its children once it completes — folding away each agent's
resolved command and cleanup note. Set collapseSubtasks: false so the
per-agent rows and their output stay visible after the group finishes.
The closing line said "Restart your AI tools to load the Sentry plugin",
which is vague when only some of the detected agents were installed.
List the agents that actually received the plugin instead, e.g. "Restart
Claude Code and Codex to use Sentry with AI". Blocked or failed agents
are excluded since there is nothing to restart for them.
Switch every harness's installed-probe from substring-grepping plain
`plugin list` output to parsing the `--json` variant and matching on the
plugin's real identity. Claude and Codex match a marketplace-qualified
id; Grok matches our repo source with no marketplace, since the source
alone cannot tell a direct repo install from one made through the "xAI
Official" marketplace.
Add an optional `cleanup` step that runs before install to remove a
conflicting or legacy Sentry plugin that would otherwise shadow ours:
Codex removes the curated `sentry@openai-curated`, and Grok uninstalls a
marketplace-installed sentry so our direct-repo install resolves.
`cleanup` returns a short description of what it removed, which the
installer surfaces as persistent task output so the removal is visible
rather than silent.
Split the single `install` method into explicit `install` and `update`
so the orchestrator picks based on what detection already found, instead
of each harness re-probing its own listing.
Add the Craft release pipeline so packages/installer can be published to
npm as @sentry/ai.
- .craft.yml configures a single npm target (public scoped access) with a
tarball-name guard. There is deliberately no github target, so a release
creates no git tags and no GitHub releases -- nothing repo-wide implying
the skills/plugins are versioned by it.
- The release branch is scoped to `release-installer/<version>` via
releaseBranchPrefix, so it reads as a package release. craft creates it
during prepare and deletes it after publish, so it is transient.
- changelogPolicy is `simple` (matching sentry-javascript) and the
changelog lives with the package at packages/installer/CHANGELOG.md. The
entry is written by hand before releasing; `auto` would sweep this repo's
skill-drift and plugin churn into the installer changelog.
- release.yml runs `craft prepare` via the Sentry release bot App token.
It must be an App token rather than GITHUB_TOKEN so the pushed release
branch triggers release-build.yml, which packs the tarball and uploads
it as the SHA-named artifact Craft's GitHub provider expects.
- The package gains `files: ["dist", "CHANGELOG.md"]`, publishConfig.access
public, and an explicit LICENSE so the published tarball is complete
without relying on pnpm copying the root LICENSE.
Publishing runs from getsentry/publish after a team lead approves the
release issue; this repo only prepares the release and builds the artifact.
Add a pnpm workspace with a `packages/installer` package (`@sentry/ai`)
that provides a single CLI for installing the Sentry plugin into any
supported AI coding assistant: Claude Code, Codex, Cursor, and Grok.
The CLI (built on citty) exposes an `install` subcommand. By default it
shows an interactive selector pre-selecting the detected agents;
`--no-interactive`/`--yes` skips the prompt and installs every detected
agent — used for CI and unattended runs.
Each agent is a self-contained harness implementing a common strategy
interface — detect, isInstalled, canInstall, install — so adding a new
agent is just another module. System access (shell, fs, platform,
homedir) is injected through a SystemDeps seam, keeping every harness
unit-testable without side effects.
Behavior:
- Already-installed agents update in place rather than erroring or
no-opping: Cursor pulls, Grok/Claude use their update commands, Codex
re-adds (idempotent; it has no update command).
- Claude refreshes its marketplace index before installing, since a fresh
CLI ships a stale index that cannot resolve the plugin.
- canInstall gates prerequisites (Cursor needs git) and reports a clear
skip instead of a cryptic failure.
- One agent failing does not abort the rest, and the CLI exits non-zero
if any selected agent failed or was blocked.
- Cross-platform: binary detection uses where/which per OS, Cursor is
located via its per-OS install path, and git paths are quoted for
Windows spaces.
Distribution:
- rolldown bundles the npm package (deps external).
- fossilize builds standalone Node SEA binaries; --hole-punch drops
non-English ICU data to cut the download ~30%.
CI:
- A smoke-test matrix (Linux, macOS, Windows) installs the real agent
CLIs and runs the installer non-interactively, asserting the plugin
lands for every detected agent.
Instrumented with the Sentry Node SDK.
The hardcoded skill-to-team mapping in this workflow is now handled by
CODEOWNERS, which gives GitHub native reviewer assignment without a
separate Actions job.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* [skill-drift] update(sentry-react-sdk): document dataCollection option (v10.55.0)
Automated drift-fix run.
Co-Authored-By: Claude (claude-sonnet-4-5) <noreply@anthropic.com>
* update(sentry-react-sdk): simplify dataCollection snippets
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* update(sentry-react-sdk): align dataCollection docs with defaults
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* update(sentry-react-sdk): correct SDK version to 10.57.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude (claude-sonnet-4-5) <noreply@anthropic.com>
Co-authored-by: Michael Hoffmann <michi@sentry.io>
* [skill-drift] update(sentry-react-router-framework-sdk): migrate sendDefaultPii to dataCollection API
Automated drift-fix run.
Co-Authored-By: Claude (claude-sonnet-4-5) <noreply@anthropic.com>
* update(sentry-react-router-framework-sdk): simplify dataCollection snippets
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* update(sentry-react-router-framework-sdk): align dataCollection docs with defaults
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude (claude-sonnet-4-5) <noreply@anthropic.com>
Co-authored-by: Michael Hoffmann <michi@sentry.io>
* [skill-drift] fix(sentry-svelte-sdk): replace deprecated sendDefaultPii with dataCollection
Automated drift-fix run.
Co-Authored-By: Claude (claude-sonnet-4-5) <noreply@anthropic.com>
* fix(sentry-svelte-sdk): simplify dataCollection snippets
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(sentry-svelte-sdk): align dataCollection docs with defaults
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(sentry-svelte-sdk): correct SDK version to 10.57.0
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude (claude-sonnet-4-5) <noreply@anthropic.com>
Co-authored-by: Michael Hoffmann <michi@sentry.io>
The xAI plugin marketplace scanner (generate-plugin-index.py) resolves
plugin manifests from MANIFEST_PATHS = ['.grok-plugin/plugin.json',
'.claude-plugin/plugin.json']. With the manifest at the repo root it was
silently ignored — components were still discovered via default paths, but
any manifest-level overrides (e.g. logo) would not have been picked up.
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
- Link each supported agent in the intro to its distribution repo.
- Note that the skill library is also browsable at skills.sentry.dev and
installable via the skills.sh installer.
* [skill-drift] fix(sentry-cocoa-sdk): app start limit + SentryObjC docs
Automated drift-fix run.
Co-Authored-By: Claude (claude-sonnet-4-5) <noreply@anthropic.com>
* fix(sentry-cocoa-sdk): address PR feedback on app start limit and SentryObjC docs
- Fix inconsistent 5s vs 180s app start threshold in troubleshooting table
- Remove experimental tag from SentryObjC (stable since 9.17.0)
- Bump SentryObjC min version to 9.17.1 for debug symbol support
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
---------
Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
Co-authored-by: Claude (claude-sonnet-4-5) <noreply@anthropic.com>
Co-authored-by: Philip Niedertscheider <phil.niedertscheider@sentry.io>
This repository is the single source of truth for skills and routing metadata,
but it is not itself an installable plugin, and each assistant (Claude Code,
Cursor, Codex, Grok) expects the plugin in a different on-disk shape. No plugin
ecosystem can consume a zip or release asset; all install from a git ref. So a
GitHub Actions workflow builds each agent's distribution from this repository
and deploys it to a dedicated repository whose root is exactly that agent's
plugin, which a marketplace points at as getsentry/plugin-<agent>.
Each agent has a `plugin-src/<agent>/` dir with its manifests, README, and a
`build.sh` that assembles the dist tree from shared repo content. Claude, Cursor,
and Grok consume the plugin at the repo root; Codex requires it under
`plugins/sentry/` and rejects the `disable-model-invocation` field the skill
tree relies on, so its build strips that field and emits a per-skill
`agents/openai.yaml` (see `hide-skills.py`). MCP config is built from the repo's
`mcp.json` source of truth: inline in Claude's manifest, and a file for the
others.
Cross-repo writes use a short-lived token from a dedicated GitHub App scoped to
contents:write on the plugin repos; the default GITHUB_TOKEN cannot push to
other repositories.
The root `.claude-plugin/`, `.cursor-plugin/`, and `.mcp.json` are retained so
existing installs that consumed the plugin from this repo's root keep working;
their READMEs announce removal on or after 2026-07-11.
Map each SDK skill directory to the GitHub team that owns the
corresponding SDK repository. This ensures the right team gets
requested for review when skill content changes.
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
* add span streaming migration skill
* platform agnostic
* fix: Use RegExp instead of glob pattern in ignoreSpans example
String matching in ignoreSpans uses substring includes, not glob
patterns. The `*` was treated as a literal character.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
* feedback
* Update skills/sentry-span-streaming/SKILL.md
Co-authored-by: Lukas Stracke <lukas.stracke@sentry.io>
---------
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Co-authored-by: Lukas Stracke <lukas.stracke@sentry.io>
* feat(instrumentation-guide): add sentry-instrumentation-guide skill
A decision-framework skill that helps an agent (or developer) choose which
Sentry signal to reach for when instrumenting code — error, span, span
attribute, log, or metric — and why the choice decides what's debuggable later.
Distilled from the "Errors, Traces, Logs, Metrics: When to Reach for What" post.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* feat(instrumentation-guide): add reference deep-dives
choosing-signals.md: per-signal detail, the four overlap tiebreakers, the
sampling-vs-filtering rationale, and the "wide events" argument.
instrumentation-examples.md: one handler instrumented end to end in Python and
JS/TS, showing span attribute vs log vs metric on the same decision.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* docs(skills): register sentry-instrumentation-guide
Add to the sentry-feature-setup router (decision bullet + table row), the
AGENTS.md and README feature tables, and regenerate SKILL_TREE.md.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* refactor(instrumentation-guide): defer signatures to sibling SDK skills
This skill decides WHAT signal to emit; the sentry-*-sdk skills own HOW
(setup, API signatures, enabling flags, min versions) in their
references/{tracing,logging,metrics}.md. Stop hardcoding docs.sentry.io
deep links and a Node-specific link in the language-neutral examples/
sampling sections — point at the matching SDK skill by name instead, so
there's one maintained source of truth and nothing to go stale.
Kept: top-level product-area links (one per signal) for orientation.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
* instrumentation-guide: align signal naming with the blog post
- Four-signals table and deep dive lead with Traces (spans named as the unit)
- Gut-check "reach for" column shows Traces / Spans
- Metrics question -> "How's this trending over time?"
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The plan job's UNKNOWN-skill check used `jq --argjson ... --argjson ...
'$wanted | map(...)'` without `-n` and without stdin. jq waited for
stdin, got EOF, applied the filter zero times, and produced no output —
silently yielding UNKNOWN="" rather than UNKNOWN="[]".
Then `jq 'length' <<<""` also produced no output, so the comparison
`[[ "" != "0" ]]` evaluated true and the workflow died with a
false-positive "Unknown skill(s) in workflow_dispatch input: " error
on every dispatch (run 26625830364).
Add `-nc` so jq treats null as the input, applies the filter once,
and produces the expected empty array. Also guard the length check
with `${UNKNOWN:-[]}` so a future empty value can't trip the same
false positive.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* feat(skill-drift): replace gh-aw workflow with matrix-fanout Claude runs
Replaces .github/workflows/skill-drift-check.{md,lock.yml} (single
sequential gh-aw run) with a plain GitHub Actions workflow that fans out
one Claude Sonnet session per SDK skill via matrix strategy.
Each per-skill job reads the last 7 days of merged PRs in the
corresponding SDK repo, opens the actually changed source files at the
merge SHA (not just the diff summary), compares against the local skill
bundle, and emits either a file diff under skills/<skill>/ or a
structured summary for the apply job to act on.
Security
- All actions pinned to a full commit SHA.
- permissions: {} at workflow root; each job grants only what it needs.
- The agent job (detect) runs with contents: read ONLY — no write tokens
reachable from the LLM session.
- Agent tool allowlist restricts Bash to a handful of gh/jq/date
subcommands; no git, curl, or arbitrary shell.
- The apply job is pure deterministic shell with a path allowlist
(skills/<this-skill>/ only) enforced before any commit, push, or PR.
- Inline reviewer assignment in the apply job since PRs opened by
GITHUB_TOKEN do not trigger downstream workflow_run events.
- Trigger surface is schedule + workflow_dispatch only.
Matrix lives in .github/skill-drift-matrix.json so adding a new SDK skill
is a one-line change.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(skill-drift): address review feedback in apply job
Four issues flagged on PR #135:
1. (Cursor Bugbot, HIGH) The `artifacts/` directory placed by
download-artifact was being picked up by `git ls-files --others
--exclude-standard` in the apply job's path-allowlist check, which
would have downgraded EVERY create_pr to an issue because no
`artifacts/...` path matches the `^skills/<skill>/` regex. Scope
both `git diff` and `git ls-files` to `-- skills/` so anything
outside that subtree (artifacts/, dotfiles, etc.) is invisible to
the allowlist scan.
2. (Sentry Bot, HIGH) On the path-allowlist and empty-diff bail-out
paths the script switched branches but did not remove the untracked
files left behind by `rsync`. The next iteration would then see
those files via `git ls-files --others` and trigger a false-positive
allowlist violation, cascading the failure across all remaining
skills. Add `git clean -fd -- skills/` at the start of each
create_pr iteration and on both bail-out paths.
3. (Cursor Bugbot, MEDIUM) `git diff --quiet HEAD` does not detect
brand-new untracked files. If the agent's only change was adding a
new `references/*.md`, the empty-diff guard would have silently
skipped the PR. Replace with an `[[ -z "$TOUCHED" ]]` check, since
TOUCHED already includes untracked files.
4. (Sentry Bot, MEDIUM) Parsing the PR number from `gh pr create`
stdout via `basename` is fragile — extra text would corrupt it and
the downstream JS reviewer-assignment step would silently skip.
Look up the number via `gh pr view "$BRANCH" --json number --jq
.number` instead, and log a `::warning::` (plus increment FAILED)
if it still cannot be determined. Also add a warning log in the JS
step when an invalid PR number is encountered.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix(skill-drift): second review pass — length caps, retries, pagination, edge cases
Addresses the round of review comments left on commit fea5d4a:
1. (sentry-warden, security) Untrusted SDK PR content reaches issue/PR
bodies via `gh issue create --body "$BODY"` and `gh pr create --body`.
Add a 256-char cap on `title` and a 10000-char cap on `body`, enforced
in both detect's validate step and apply's re-validation. Prepend a
GitHub `> [!WARNING]` alert header to every issue/PR body the agent
produces so human reviewers see "Auto-generated from external SDK
content. Review all links and code suggestions before acting on
them." before reading agent-authored text. Limits the blast radius
of a successful prompt-injection from an SDK PR.
2. (sentry-bot, MEDIUM) Race condition between `gh pr create` and
`gh pr view`. GitHub's API is eventually consistent — the immediate
view can 404 and silently skip reviewer assignment. Retry up to 5
times with a 1s sleep; warn (not silently skip) if all attempts fail.
3. (cursor-bot, MEDIUM) `actions/download-artifact@v4` fails the step
when its pattern matches zero artifacts, before the apply script's
graceful empty-artifact branch can run. Mark the download step
`continue-on-error: true`. The apply script already handles a
missing `artifacts/` directory and now also emits a complete set of
step-outputs (prs/issues/no_drift/failed/opened_pr_nums) on the
no-artifacts early-exit so downstream conditions see consistent
values.
4. (cursor-bot, LOW) `github.rest.pulls.listFiles` in the inline
reviewer-assignment step only returns the first page. Use
`github.paginate(..., per_page: 100)` so PRs with many files don't
miss teams. Matches the convention used by
skill-drift-assign-reviewers.yml.
5. (cursor-bot, LOW) Trailing/leading/double commas in the
`workflow_dispatch` `skills` input parsed as empty tokens, which
then failed the unknown-skill check. Drop empty entries in the jq
pipeline.
6. (cursor-bot, MEDIUM) `title` and `body` were validated as strings
but empty strings passed, which would have broken `git commit -m ""`
and yielded useless empty issues. Require non-empty values for
`create_pr`/`create_issue` (still allowed for `no_drift`, which the
apply job ignores entirely).
WONTFIX: the "hardcoded `main`" comment. This workflow is pinned to
`getsentry/sentry-for-ai` via `if: github.repository ==` guards on
every job; the default branch IS `main`. Parameterizing
`github.event.repository.default_branch` adds complexity for a
portability story we don't have.
Also: update the agent prompt with the new length caps and non-empty
requirement so the agent's output isn't silently rejected by
validation.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>