Monorepo with 40+ packages in @sentry/*, managed with Yarn workspaces and Nx.
- Volta for Node.js/Yarn/PNPM version management
- Requires
VOLTA_FEATURE_PNPM=1 - After cloning:
yarn install && yarn build - Never change Volta, Yarn, or package manager versions unless explicitly asked
Prefer LSP over Grep/Read for code navigation — it's faster, precise, and avoids reading entire files:
workspaceSymbolto find where something is definedfindReferencesto see all usages across the codebasegoToDefinition/goToImplementationto jump to sourcehoverfor type info without reading the file
Use Grep only when LSP isn't available or for text/pattern searches (comments, strings, config).
After writing or editing code, check LSP diagnostics and fix errors before proceeding.
Use yarn, never npm or pnpm. Scripts live in the root package.json.
yarn build:dev:filter @sentry/<pkg> builds one package and its deps.
Single package: cd packages/<name> && yarn test
AI commits MUST include a Co-Authored-By line with the appropriate committer email when known:
Co-Authored-By: <Claude model name> <noreply@anthropic.com>
Co-Authored-By: <OpenAI/ChatGPT model name> <codex@openai.com>
Co-Authored-By: <Cursor agent name> <cursoragent@cursor.com>
Use the Cursor email for Cursor, even when it runs a Claude or OpenAI model. Omit the line only when there is no known committer email address for the agent.
Uses Git Flow (see docs/gitflow.md).
- All PRs target
develop(NOTmaster) master= last released state — never merge directly- Feature branches:
feat/descriptive-name - Never update dependencies,
package.json, or build scripts unless explicitly asked
yarn formatyarn build:devyarn lintyarn test- NEVER push on
develop
- Do NOT add a "Test plan" / "Testing" checklist to PR bodies. CI runs the full test suite on every PR — a hand-rolled checklist duplicates that signal and rots fast. Write the summary content directly and add a Root cause section only if relevant.
- Omit the "Summary" heading in PR bodies — lead with the summary text itself, no
## Summaryheader. - Include
Fixes #<issue-number>somewhere in the PR body so the merge auto-closes the linked issue. - Always open PRs as draft.
- Keep PR descriptions condensed — usually a few sentences. Explain the reasoning: why the change is needed, and any non-obvious decisions or tradeoffs made along the way. Do not walk through the implementation or describe what changed file-by-file — the diff already shows that. Only go longer when the decisions genuinely warrant it.
- Types live in
packages/core/. The@sentry/typespackage is gone. - An AI provider integration spans two places, both in
packages/server-utils/: the gen-AI instrumentation logic insrc/ai/{provider}/, and the integration that wires it up insrc/integrations/{provider}.ts, registered ingetTracingIntegrations(). Runtime packages (node,cloudflare, ...) re-export from@sentry/server-utilsrather than defining their own.
- This project uses Oxlint and Oxfmt — NOT ESLint or Prettier
- Never run
eslint,npx eslint, or any ESLint CLI — useyarn lint(Oxlint) instead - Never run
prettier— useyarn format(Oxfmt) instead - ESLint packages in the repo are legacy/e2e test app dependencies — ignore them
- Do not create, modify, or suggest
.eslintrc,eslint.config.*, or.prettierrcfiles
- Follow existing conventions — check neighboring files
- Reach for existing utils before writing a new one. Most shared helpers live in
@sentry/core(packages/core/src/utils/), with browser helpers inpackages/browser-utils/. Search first (LSPworkspaceSymbolor grep) for common needs (type guards inis.ts, object/array helpers,normalize,dsn,merge, string/url helpers). Reuse or extend the existing util rather than adding a near-duplicate; only introduce a new util when nothing fits. - Only use libraries already in the codebase
- Never expose secrets or keys
- When modifying files, cover all occurrences (including
src/andtest/) - Write few comments; default to none. Comments explain why, never what — never add a comment that restates what the code does or narrates the change being made. Only add one when the reasoning isn't clear from the code itself, or to flag a tradeoff or something that would otherwise look surprising to a reader. When in doubt, leave it out.
- Do not use
expect(someSpy.mock.calls[0]?.[0])or similar constructs to check what a spy was called with. Instead useexpect(someSpy).toHaveBeenCalledWith(...)or derivatives for a more readable and less brittle test assertion.
- Do not set
traceLifecyclein new tests (node-integration-tests, e2e). Leave it unset so the test exercises the default — span streaming (stream). Many existing suites pintraceLifecycle: 'static'(or toggle it viaprocess.env.STREAMED); do not copy that into new tests. - Under span streaming there is no
transactionenvelope with nestedspans. Assert the transaction via the streamed span container:.expect({ span: (container) => { ... } }), finding the root withcontainer.items.find(i => i.is_segment)and child spans withcontainer.items.filter(...). Add.unordered()so the span container and error events can arrive in any order and unrelated envelopes (client reports, etc.) are ignored. Errors are still separateeventenvelopes (.expect({ event })). A streamed span's error status is'error', not the classic transaction's'internal_error'.
Do NOT "fix" a bundler, runtime, or platform incompatibility by making an import lazy or opaque — createRequire, require-inside-a-function, dynamic import(), computed specifiers. Not all bundlers understand createRequire, and anything opaque to static analysis just moves the breakage to a different consumer (pnpm isolation, workerd, Turbopack, nft tracing) while masking the real defect. SDK code must stay statically analyzable.
Before even proposing lazy loading:
- Reproduce the failure and read the actual error — not a plausible theory about it. If the error is swallowed, extract it (debug logging, running the server/bundle directly) before choosing a fix.
- Fix the root cause at the layer it lives in, in roughly this order: build output shape (rollup/commonjs options like
interop,strictRequires,requireReturnsDefault,output.paths), module resolution (exportsmaps, self-references, absolute-path externals), packaging (what ships in the tarball, bundled vs external deps), and only then consumer-side configuration. - If, after exhausting these, lazy loading still seems necessary, stop and ask — explain what was tried and why nothing else works. Do not implement it first.
Task-specific instructions live in .claude/skills/. Each skill lists its
own trigger, so consult that directory rather than this file.