Repository navigation
feat(cli): Cache telemetry that fails to send and retry it on the next run - #1963
Open
sentry-junior[bot] wants to merge 8 commits into
Open
sentry-junior[bot] wants to merge 8 commits into
sentry-junior[bot] wants to merge 8 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
BYK
added a commit
that referenced
this pull request
Nov 4, 2026
## Summary Remove every way to override the DSN for the server's own telemetry in `sentry mcp` and `@sentry/mcp-server`: - the `--sentry-dsn` flag; - the `SENTRY_DSN` and `DEFAULT_SENTRY_DSN` env reads; - the `sentryDsn` config plumbing. MCP telemetry now always goes to `SENTRY_CLI_DSN`. Only the CLI's telemetry opt-outs decide whether it's on. `SENTRY_DSN` is the variable people set for their own apps. An MCP server started from a project shell or IDE inherited it and sent our telemetry to the user's own Sentry project. The override dates back to #120 (Apr 2025), which added Sentry to the original stdio package with `--sentry-dsn`/`SENTRY_DSN`. In that design, an empty `--sentry-dsn=` was the documented opt-out. #1401 (`sentry mcp`) and #1731 (the slim `@sentry/mcp-server` bundle) carried it over. ### Key changes - **`packages/cli/src/lib/mcp/`:** - `args.ts` drops the flag type, the env reads and the merge. - `config.ts` drops `sentryDsn`. - `server.ts` always calls `init({ dsn: SENTRY_CLI_DSN, enabled: isTelemetryEnabled() })`. - The early return for an empty `--sentry-dsn=` is gone, and so is the rule that an explicit DSN ignored the CLI opt-out. - **`commands/mcp.ts`:** the `--sentry-dsn` flag and the `SENTRY_DSN` help line are removed. The skill reference is regenerated with `generate:skill`. - **`mcp-test-client`:** stops passing `--sentry-dsn` and `SENTRY_DSN` to the server it launches. Its own `--sentry-dsn`, for the test client's own Sentry client, stays, with a clearer description. - **`mcp-core/tsdown.config.ts`:** drops the unused `DEFAULT_SENTRY_DSN` define. Nothing in mcp-core reads it; it was the old stdio package's build-time DSN. - **Docs:** `docs/operations/monitoring.md`, `docs/testing/stdio.md`, `TELEMETRY.md`, the mcp-core README and the stdio setup page now point to the opt-out instead. - The remote/Cloudflare `VITE_SENTRY_DSN` is unchanged. That's deployment config, not a user override. ### Breaking changes **For `@sentry/mcp-server` and `sentry mcp` users:** - **`--sentry-dsn` is now an unknown flag.** A config that passes it, including the documented opt-out `--sentry-dsn=`, fails before the server starts. I checked on the rebuilt bundle (`node dist/sentry-mcp.cjs --sentry-dsn=` and `--sentry-dsn=https://…`): stderr shows `No flag registered for --sentry-dsn` and the exit code is 252. - An error is safer than silently ignoring the flag. Ignoring it would quietly turn telemetry back on for anyone who had opted out. - **`SENTRY_DSN` and `DEFAULT_SENTRY_DSN` are silently ignored.** Anyone who used them to send MCP telemetry to their own Sentry instance now reports to the Sentry CLI's project instead. **Migration:** remove `--sentry-dsn` from the MCP config. To turn telemetry off, set `SENTRY_CLI_NO_TELEMETRY=1` or `DO_NOT_TRACK=1` in the server's `env`, or run `sentry cli defaults telemetry off` once (shared config dir). ```json { "command": "npx", "args": ["@sentry/mcp-server@latest"], "env": { "SENTRY_CLI_NO_TELEMETRY": "1" } } ``` `computeTelemetryEffective()` in `packages/cli/src/lib/telemetry.ts` checks the env vars for an exact `"1"`, then the stored preference. **Release note:** the title has no `!`. `.craft.yml` uses `changelog.policy: auto` and `versioning.policy: auto`, there's no `.github/release.yml`, and Craft's default categories map `type!:` to a major bump. A `!` would most likely cut CLI 1.0.0 (inferred from Craft's docs, not tried). `docs/releases/stdio.md` has no separate breaking-change process for `@sentry/mcp-server`, which ships at the CLI's version. Please call out the break in the release notes, or add `!` if a major bump is intended. ### Tests - `test/lib/mcp/telemetry.test.ts`: - init always uses `SENTRY_CLI_DSN`; - `SENTRY_CLI_NO_TELEMETRY=1` and `DO_NOT_TRACK=1` each disable it; - with `SENTRY_DSN` and `DEFAULT_SENTRY_DSN` set in the env, the DSN is still the CLI's (the env leak behind this change). - `test/lib/mcp/args.test.ts`: `parseMcpEnv` ignores both env vars, and `--sentry-dsn=` is rejected. - `packages/mcp-server/test/bundle.test.mjs`: - the bundle refuses `--sentry-dsn=`; - the "telemetry on" exit test turns telemetry on with `SENTRY_CLI_NO_TELEMETRY=0` and sends it to a dead `https_proxy`, so nothing leaves the machine. ### Screenshot The stdio setup page (`packages/mcp-cloudflare`, `?transport=stdio`), from a local `vite build`:  ### Interaction with #1963 #1963 stacks the offline telemetry store on #1960. It gates the store in `mcp/server.ts` on `useCliDsn`, which is true only without an override. Once this merges, that gate is always true and becomes dead code. Whichever PR lands second removes it and always uses the store. <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3A11063284b105b81f348414f8900e5707 --> -- [View Session in Sentry](https://sentry.sentry.io/explore/conversations/agent%3A11063284b105b81f348414f8900e5707/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
Base automatically changed from
junior/1956-telemetry-request-deadline
to
main
November 4, 2026 23:16
sentry-junior
Bot
force-pushed
the
junior/telemetry-offline-store
branch
from
November 4, 2026 23:19
ce21980 to
39b47e7
Compare
sentry-junior
Bot
force-pushed
the
junior/telemetry-offline-store
branch
from
November 4, 2026 23:20
39b47e7 to
5992dab
Compare
sentry-junior
Bot
force-pushed
the
junior/telemetry-offline-store
branch
from
November 4, 2026 23:21
5992dab to
39b47e7
Compare
Contributor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 39b47e7. Configure here.
…t run With ingest stalled, telemetry sent during a command timed out, and the exit flush then sent new data that timed out again: about 6.6 s to exit, with everything dropped. Wrap the telemetry transport with the SDK's makeOfflineTransport, backed by a new SQLite table (schema v21). An envelope whose send fails or times out, or gets a 5xx, is stored, and a per-process breaker sends every later envelope straight to disk. The next run claims up to 10 stored envelopes with DELETE ... RETURNING and sends them in the background; the exit flush waits for them. The store keeps at most 100 envelopes and 4 MiB, and drops anything older than 4 days, under Relay's 5-day limit for sessions and transactions. Opting out clears it; library mode never uses it. With ingest stalled, a command now exits in about 3.3 s, and a later run delivers what it stored. Refs #1956
… cache sentry mcp telemetry The breaker now half-opens 5 minutes after it opens. The next envelope is sent as a probe: a response closes the breaker and sends a batch of stored envelopes, and a failure opens it for another 5 minutes. Short commands never live that long, so they still wait for one deadline and then write to disk. Long-running ones, such as `sentry mcp` and `sentry local`, now deliver what they stored once ingest comes back. A 5xx response is dropped like a 4xx instead of being stored and opening the breaker. The SDK records no outcome for it, and the CLI's client reports show network errors as the real loss. Envelopes with an error event keep 30 days, since Relay accepts errors for the project's retention; everything else keeps 4 days. Over a cap, the store drops envelopes without an error first. `sentry mcp` uses the store when it reports to the CLI's project (no `--sentry-dsn` or `SENTRY_DSN`): it sends stored envelopes at startup and clears them when telemetry is off. Refs #1956 Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>
Inserting an envelope over the byte cap put every row over it in the trim query, so the store deleted everything it held. Drop the oversized envelope instead; it could never be kept. Refs #1956
…g them A long-running command emits runtime metrics every few seconds. While ingest was down they filled the store and pushed sessions and transactions out of its caps. They are now sent as usual and dropped on failure; a metrics envelope can still be the probe that closes the breaker. Refs #1956
In a parallel drain batch, a response that arrived after another send failed re-closed the breaker and claimed another batch, so a flaky ingest could walk through the store, dropping any 4xx or 5xx. Only the probe now closes the breaker. Refs #1956
The write-path trim ranked expired rows along with live ones in the same DELETE. Expired errors sort first, so they could push a new envelope past the count or byte cap and delete it in the statement that removed them. Delete expired rows in their own statement first, as claim() does. Refs #1956
sentry-junior
Bot
force-pushed
the
junior/telemetry-offline-store
branch
from
November 4, 2026 23:39
2eb3ff8 to
3319004
Compare
…lopes The cooldown comment and the docs said a long-running command sends everything it stored once ingest recovers. It sends one batch of 10 on purpose, so each recovery stays bounded; the rest wait for later runs. Refs #1956
This branch was previously deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Refs #1956
Stacked on #1960 (base
junior/1956-telemetry-request-deadline); merge it after #1960.With ingest stalled, #1960 gets a command out in about 6.6 s, and the telemetry it tried to send is dropped. This PR keeps that telemetry on disk and retries it later. It also stops hitting the network after the first failure, so a command's exit waits for one request deadline instead of two.
Design
telemetry/offline-store.ts, newtelemetry_envelope_queuetable, schema v21).serializeEnvelopeas BLOBs, since attachments can be binary, and read back withparseEnvelope. A row that no longer parses is dropped.is_errormarks envelopes that contain aneventitem. Each row's max age depends on it.DELETE ... WHERE id IN (SELECT ... LIMIT n) RETURNING, so two CLI processes never get the same envelope.telemetry/offline-transport.ts).makeOfflineTransportfrom@sentry/corewraps the zstd transport, which keeps fix(cli): Let the CLI exit on its own instead of forcing it on macOS #1960's 3 s request deadline. Redaction still runs first, so stored envelopes are already redacted.shouldStorereturns false fortrace_metricenvelopes. They're sent as usual and dropped on failure, or skipped while the breaker is open. A long-running command emits runtime metrics every 5 s, and old ones are worth little. When they were stored, they filled the store during an outage and pushed sessions and transactions out of the caps. A metrics envelope can still be the probe that closes the breaker.shouldSendreturns false and envelopes go straight to disk.BREAKER_COOLDOWN_MS(5 minutes), the next envelope goes out as a probe. Envelopes sent while the probe is in flight go to disk.withTelemetry(), the CLI claims up to 10 stored envelopes and sends them without awaiting. Theclose(3000)on exit waits for them. Each resend gets a freshsent_at: Relay shifts item timestamps by the gap betweensent_atand receipt, so a stalesent_atwould move them forward.sentry mcpuses the store only when it reports to the CLI's project, that is with no--sentry-dsnorSENTRY_DSN(useCliDsn). Then it wraps fix(cli): Let the CLI exit on its own instead of forcing it on macOS #1960'smakeNodeTransportWithDeadline, sends up to 10 stored envelopes at startup, and recovers through the breaker.close(5000)on disconnect is unchanged. With an override, it keeps the plain transport. The gate is small on purpose: removing the override is happening in a separate PR on main.withTelemetry()andsentry mcpdelete the stored envelopes. They check for rows first, so an opted-out run against a read-only database prints no warning.SDK behavior this relies on (
@sentry/core11.6.0,transports/offline.jsandbase.js):{}. IfshouldSendreturns false, the SDK throws internally, so that envelope is stored too.base.jsrecords an outcome only for 413 (send_error) and for thrown errors (network_error). In the CLI project over the last 30 days, client reports shownetwork_erroron about 82k errors, 15k sessions, 13k transactions, and 106 logs, andsend_error(413) on 88 transactions. The loss is network errors, which the store covers.shouldStorereturns false (our metrics rule), the SDK rethrows the error instead of storing.client.sendEnvelopecatches it.createTransportdrops rate-limited items before the send, so they are never stored.shiftalways returns nothing, which turns off the SDK's timer-driven retries. Those send one envelope 100 ms after each success, or on a backoff starting at 5 s. When one fires during the exit flush, its request keeps the process alive past the flush, and such requests can chain.sendStoredreplaces them.Limits
max_secs_in_pastto the project's retention (90 days by default,relay-server/src/processing/utils/event.rs), and past that it only replaces the timestamp with the received time.max_session_secs_in_past).max_secs_in_past.Measured on the rebuilt npm bundle (
dist/bin.cjs), Node 22, Linux:NODE_EXTRA_CA_CERTS) and answered 200.helpcli defaultshelpsentry local serve, stopped before the cooldown:sentry local serve, run past the cooldown (re-run after metrics stopped being stored):helpruns (3.21–3.38 s each) left 8 envelopes in the store: 4 transactions and 4 sessions.local servestarted with ingest stalled, and ingest turned healthy at 20 s. The startup drain timed out and requeued its envelopes. With the breaker open, the store grew to only 9; the metrics emitted every 5 s were dropped.sent_at: sessions, transactions and a log.trace_metric.SENTRY_CLI_NO_TELEMETRY=1after stalled runs left the store empty.sentry mcp: on this branch the bundle sends no telemetry, because of a separate bug on main:Sentry.initruns insidebuildCommand's pre-initexecspan, so the client is lost. fix(cli): Bind the sentry mcp telemetry client so it sends anything #1970 fixes it. Until fix(cli): Bind the sentry mcp telemetry client so it sends anything #1970 merges, this PR's MCP path is covered by unit tests only.Not run locally: six unrelated permission tests (EACCES/EPERM) fail in this sandbox because it runs with full capabilities. They fail the same way on #1960.
Open questions:
completion_telemetry_queue) are alsotrace_metric, so an outage now drops them, as it did before this PR. OK to leave them out of the store?via @BYK.
--
View Session in Sentry