Imported from getsentry/sentry-dotnet (
AGENTS.md). Install upstream withnpx skills add getsentry/sentry-dotnet. Copyright stays with the author.
Agent Instructions
The Sentry SDK for .NET: a multi-package repo (Sentry, Sentry.AspNetCore, Sentry.Maui,
Sentry.Serilog, Sentry.OpenTelemetry, ~25 packages under src/) built with the .NET SDK
pinned in global.json. It spans .NET Framework through the latest .NET, plus Android, iOS
and Mac Catalyst via bindings over the native Sentry SDKs — see Platform Targets
for the target frameworks and per-platform toolchain requirements.
Getting Started
Check out the submodules first. The native SDKs and several build/tooling projects live in git submodules under modules/ (e.g. sentry-native, sentry-cocoa, perfview). Without them, builds, solution-filter generation, and other tooling break in confusing ways — e.g. scripts/generate-solution-filters.ps1 silently drops the missing projects from the *.slnf files.
The bootstrap step handles this for you (it runs git submodule update --init --recursive plus a full restore):
./dev.cs cleanslate
If you only need to sync submodules (e.g. in a fresh worktree), run ./dev.cs subup or git submodule update --init --recursive directly.
Build System
Uses the .NET SDK (dotnet CLI). Key files:
Sentry.slnx— full solution (all platforms/samples)... rarely used directlyglobal.json— pins .NET SDK and Workload versionsDirectory.Build.props/Directory.Build.targets— shared MSBuild properties across all projects
MSBuild hierarchical structure
MSBuild searches upward from each project file and uses the nearest Directory.Build.props/.targets it finds. Parent files are not automatically included. props/targets files in this repo explicitly <Import> parent/root files when appropriate.
integration-test/Directory.Build.props / integration-test/Directory.Build.targets are intentionally isolated (do not import root).
Solution Filters
Use solution filters instead of the full Sentry.slnx:
| Filter | Use when |
|---|---|
SentryNoMobile.slnf |
General development (no Android/iOS build toolchain needed for quicker builds) |
Sentry-CI-Build-macOS.slnf |
All macOS projects |
Sentry-CI-Build-Windows.slnf |
All Windows projects |
Sentry-CI-Build-Linux.slnf |
All Linux projects |
Do not edit
*.slnffiles directly. They are generated byscripts/generate-solution-filters.ps1fromscripts/generate-solution-filters-config.yaml.
Essential Commands
# Full build (operating system specific - run one only)
dotnet build Sentry-CI-Build-macOS.slnf
# Quick build (no mobile targets)
dotnet build SentryNoMobile.slnf
# Build a specific project
dotnet build src/Sentry/Sentry.csproj
# Run all tests
dotnet test Sentry-CI-Build-macOS.slnf
# Run all tests (non-mobile)
dotnet test SentryNoMobile.slnf
# Run tests for a specific project
dotnet test test/Sentry.Tests/Sentry.Tests.csproj
# Run a single test by name
dotnet test test/Sentry.Tests/ --filter "FullyQualifiedName~CaptureEvent_"
# Run android device tests (Android emulator must be running)
pwsh scripts/device-test.ps1 android
# Run ios device tests (iOS simulator must be running)
pwsh scripts/device-test.ps1 ios
# Format code
dotnet format Sentry.slnx --no-restore --exclude ./modules --exclude ./**/*OptionsSetup.cs --exclude ./test/Sentry.Tests/AttributeReaderTests.cs
Before you're done
A change isn't finished until these are clean:
dotnet build Sentry-CI-Build-macOS.slnf # or the .slnf matching your OS
dotnet test Sentry-CI-Build-macOS.slnf
dotnet format Sentry.slnx --no-restore --exclude ./modules --exclude ./**/*OptionsSetup.cs --exclude ./test/Sentry.Tests/AttributeReaderTests.cs
git status # see the note below
git status matters because the API-approval tests call Verify's AutoVerify
(test/Sentry.Testing/ApiExtensions.cs): a local test run silently rewrites the
*.verified.txt snapshots and still reports green. Check for snapshot churn after any run that
touches public API, and commit it deliberately.
For a full Release-mode build and test — what CI runs — use ./build.sh (./build.cmd on
Windows).
Boundaries
🚫 Never edit these — generated, vendored, or guarded by CI
| Path | Instead |
|---|---|
*.slnf |
Edit scripts/generate-solution-filters-config.yaml, then run scripts/generate-solution-filters.ps1 |
CHANGELOG.md |
Generated on squash-merge from the PR title. .github/workflows/changelog-guard.yml fails CI on manual entries |
src/Sentry.Bindings.Cocoa/ApiDefinition.cs, StructsAndEnums.cs |
Edit scripts/patch-cocoa-bindings.cs, then run scripts/generate-cocoa-bindings.ps1 |
modules/** |
Git submodules pointing at other repositories — change them upstream, never in place |
*.verified.* |
Run the tests, then pwsh ./scripts/accept-verifier-changes.ps1 |
🚫 Never commit secrets
Never commit auth tokens, API keys or credentials. The DSNs checked into samples/ point at
Sentry's own demo projects — don't swap in one from a real project.
⚠️ Ask first
- Breaking or removing public API (see Key Conventions)
- Adding a package dependency — the SDK deliberately keeps its dependency surface minimal
- Changing anything under
.github/workflows/
Repository Layout
src/ # Source for all Sentry packages
test/ # Test projects (mirror src/ naming: Sentry.X.Tests)
samples/ # Sample applications
benchmarks/ # Performance benchmarks
integration-test/ # Pester-based integration tests (CLI, AOT, runtime, etc.)
modules/ # Native SDK submodules (sentry-native, Ben.Demystifier, etc.)
scripts/ # Build and maintenance scripts
Platform Targets
Non-mobile (Linux / macOS / Windows)
- Use
SentryNoMobile.slnf— no extra toolchain needed. - Targets:
net9.0,net10.0,netstandard2.0,netstandard2.1,net462.
Android
- Requires
JAVA_HOMEset and Java installed. - Built on all platforms (Linux, macOS, Windows).
Sentry.Bindings.Androidwraps the native Android SDK.
iOS / Mac Catalyst
- macOS only. Requires Xcode.
Sentry.Bindings.Cocoawraps the native Cocoa SDK.- Device tests run in CI only.
src/Sentry.Bindings.Cocoa/ApiDefinition.csandStructsAndEnums.csare auto-generated — do not edit them directly. All changes must go inscripts/patch-cocoa-bindings.csand be applied by runningscripts/generate-cocoa-bindings.ps1.
MAUI
- Requires MAUI workloads:
sudo dotnet workload restore(macOS/Linux) ordotnet workload restore(Windows). - Combine mobile toolchain requirements above.
Blazor WASM
Sentry.AspNetCore.Blazor.WebAssembly.PlaywrightTestsuses Playwright — seeplaywright-blazor-wasm.ymlfor CI setup.
Alpine / musl
- Separate CI pipeline (
alpine.yml). Do not attempt Alpine-specific builds outside that environment.
Testing Conventions
- Framework: xUnit
- Mocking: nsubstitute
- Test project naming:
<SourceProjectName>.Tests(e.g.Sentry.AspNetCore.Tests) - Test naming convention:
Method_Context_Expectation(e.g.,CaptureEvent_ActiveScope_AppliesScopeData) - Snapshot tests use Verify — commit updated
*.verified.*files when API surface changes - Device tests (
Sentry.Maui.Device.TestApp,AndroidTestApp); requires a connected emulator to run locally - Integration tests in
integration-test/use Pester (PowerShell) — require a local nuget pack or CI-built packages
Accepting Verify snapshot changes
dotnet test
pwsh ./scripts/accept-verifier-changes.ps1
# Repeat if needed — dotnet test stops after N failures
API Changes
Public API diffs are stored as Verify snapshot files. After any public API change:
- Run tests locally — they will fail with a diff
- Accept the diff:
pwsh ./scripts/accept-verifier-changes.ps1 - Commit the updated
*.verified.*snapshot files
Git Workflow
- Branch off
main. Open the PR as a draft while you work on it, and mark it ready once CI is green and tests are in place. - Link the issue with a plain auto-close keyword on its own line in the PR body —
Closes #1234, not inline code and not a bare URL — so GitHub registers the link. - Never
git add -A. After mergingmain, the recorded submodule pointers move but the working copies undermodules/do not until you rungit submodule update --init --recursive.git add -Athen stages the stale copies and silently reverts a submodule pointer inside a commit that has nothing to do with submodules. Stage explicit paths instead, and check the full delta before pushing:
git diff origin/main...HEAD --stat # should list only files you meant to touch
PR titles are changelog entries
The PR title becomes the squash-merge commit message and, from there, the changelog line. It must be a conventional commit that stands on its own as a release note:
feat: allow users to control Mechanism.Handled for captured exceptions
fix(serilog): logs from application namespaces beginning with "Sentry" are discarded
chore(deps): update Native SDK to v0.16.3
The prefix selects the CHANGELOG section (defined in .github/release.yml):
| Prefix | Section |
|---|---|
feat: |
Features ✨ |
fix: / bugfix: |
Fixes 🐛 |
chore(deps): |
Dependencies ⬆️ |
any type with !, e.g. feat!: |
Breaking Changes 🛠 |
anything else (ci:, docs:, deps:, meta:, …) |
Other |
Changelog
CHANGELOG.md is generated — see Boundaries.
Do NOT add entries manually. For a change that isn't user-facing, put #skip-changelog in
the PR description or add the skip-changelog label.
# Get PR number for current branch
gh pr view --json number -q '.number'
Commit Attribution
AI commits MUST include:
Co-Authored-By: <Agent Name> <agent-email-or-noreply@example.com>
Key Conventions
- New features must be opt-in — extend
SentryOptionsor relevant options class with getters/setters - Maintain backwards compatibility — avoid breaking public API without strong justification
- Platform-specific code lives in
src/Sentry/Platforms/and is conditionally compiled
Code Style
.editorconfig is authoritative for formatting and analyzer severity. dotnet format (see
Essential Commands) enforces it — run it before pushing rather than
hand-formatting.
Prefer no comments
This repository favours clean, readable code that needs no comments at all. Reach for a better name or a smaller method before reaching for a comment. Where something genuinely isn't obvious — a non-intuitive framework behaviour, a workaround for an upstream bug — a minimal comment is fine, but the code and the PR description are the documentation.
Do not add comments that restate what the code already says. In particular, don't annotate
members as being exposed for tests (// Exposed for tests) — that's already apparent from
the member being internal and from tests being its only callers.
❌ Comments that restate the code, or explain a member's visibility:
// Exposed for tests
internal bool HasFlushed { get; private set; }
// Loop through the envelope items and send each one
foreach (var item in envelope.Items)
{
await SendItemAsync(item, cancellationToken).ConfigureAwait(false);
}
✅ Names carry the meaning; a comment earns its place only where the code can't speak:
internal bool HasFlushed { get; private set; }
foreach (var item in envelope.Items)
{
await SendItemAsync(item, cancellationToken).ConfigureAwait(false);
}
// Method info is currently only exposed by ToString(), see https://github.com/dotnet/runtime/issues/92869
internal static SentryStackFrame ParseNativeAOTToString(string info)
(that last one is real, from src/Sentry/Internal/DebugStackTrace.cs — it records an upstream
runtime limitation the code itself can't convey.)
Adding New Options (AOT Compatibility)
SentryOptions is not bound directly from configuration. Instead, a parallel BindableSentryOptions class (src/Sentry/BindableSentryOptions.cs) exists for AOT-safe configuration binding.
When adding a configurable property to any of the classes descending from SentryOptions:
- Add the property to
SentryOptionsas normal. - Add a matching nullable property to
BindableSentryOptions. Use only simple/primitive types the source generator can handle. For complex types (e.g.,IReadOnlyList<StringOrRegex>), use a simpler surrogate (e.g.,List<string>?) and convert inApplyTo. - Add a line in
BindableSentryOptions.ApplyTo:options.MyProp = MyProp ?? options.MyProp; - Run the relevant bindable options test (e.g.,
BindableSentryOptionsTests) — theBindableProperties_MatchOptionsPropertiestest will fail if any bindable property is missing from the bindable class.
The same pattern applies to BindableSentryAspNetCoreOptions, BindableSentryMauiOptions, BindableSentryLoggingOptions, and the platform-specific partial classes under src/Sentry/Platforms/.
Code Intelligence
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 source
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.