Sentry CLI can automatically detect your project’s DSN by scanning source code, environment files, and environment variables. This enables zero-configuration workflows where the CLI infers the target project from your codebase.
Detection Priority
DSNs are detected in priority order (highest to lowest):
- Source code - Explicit DSN in
Sentry.init() calls
- Environment files -
.env, .env.local, .env.production, etc.
- Environment variable -
SENTRY_DSN
Higher-priority sources override lower-priority ones. For example, a DSN in source code takes precedence over .env files.
Source Code Detection
The CLI scans source files for DSN patterns in Sentry.init() calls:
Supported Languages
JavaScript/TypeScript:
Python:
Go:
Java:
SentryOptions options = new SentryOptions();
options.setDsn("https://[email protected]/456");
Ruby:
PHP:
Detection Algorithm
The code scanner uses language-specific patterns:
// From src/lib/dsn/code-scanner.ts
export async function scanCodeForFirstDsn(
cwd: string
): Promise<DetectedDsn | null> {
for (const detector of languageDetectors) {
const pattern = `**/*${detector.extensions.join(',')}`;
const glob = new Bun.Glob(pattern);
for await (const file of glob.scan({ cwd })) {
// Skip node_modules, vendor, etc.
if (shouldSkipPath(file, detector.skipDirs)) {
continue;
}
const content = await Bun.file(join(cwd, file)).text();
const dsn = detector.extractDsn(content);
if (dsn && parseDsn(dsn)) {
return createDetectedDsn(dsn, "code", file);
}
}
}
return null;
}
The scanner skips common directories like node_modules/, vendor/, .git/, dist/, and build/ to avoid false positives and improve performance.
Environment File Detection
The CLI searches for DSN in environment files following the priority order:
.env.local
.env.development.local
.env.production.local
.env.test.local
.env
.env.development
.env.production
.env.test
DSN can be specified in various formats:
Parsing Logic
The environment file parser handles various formats:
// From src/lib/dsn/env-file.ts
export function extractDsnFromEnvContent(content: string): string | null {
const lines = content.split('\n');
for (const line of lines) {
const trimmed = line.trim();
// Skip comments and empty lines
if (!trimmed || trimmed.startsWith('#')) {
continue;
}
// Match SENTRY_DSN=value pattern
const match = /^export\s+SENTRY_DSN\s*=\s*['"]?([^'"\s]+)['"]?/i.exec(trimmed)
|| /^SENTRY_DSN\s*=\s*['"]?([^'"\s]+)['"]?/i.exec(trimmed);
if (match?.[1]) {
return match[1];
}
}
return null;
}
Environment Variable Detection
As a fallback, the CLI checks the SENTRY_DSN environment variable:
This has the lowest priority and is checked only if no DSN is found in code or environment files.
Project Root Detection
Before scanning for DSNs, the CLI must determine the project root. It walks up from the current directory looking for markers:
VCS Markers
Language Markers
JavaScript/Node.js:
package.json
tsconfig.json
jsconfig.json
Python:
pyproject.toml
setup.py
setup.cfg
requirements.txt
Pipfile
Go:
Java:
pom.xml
build.gradle
build.gradle.kts
Ruby:
PHP:
Rust:
Detection Flow
Finding a DSN in .env during walk-up stops the walk (determines project root) but does NOT short-circuit detection - the CLI still scans for code DSNs which have higher priority.
Caching Strategy
Detection results are cached in SQLite to avoid repeated scans:
DSN Cache Schema
CREATE TABLE dsn_cache (
project_root TEXT PRIMARY KEY,
dsn TEXT NOT NULL,
project_id TEXT,
org_id TEXT,
source TEXT NOT NULL,
source_path TEXT,
fingerprint TEXT,
updated_at INTEGER NOT NULL
);
Cache Validation
Cached entries are validated before use:
// From src/lib/dsn/detector.ts
async function verifyCachedDsn(
cwd: string,
cached: CachedDsnEntry
): Promise<DetectedDsn | null> {
// For env var source, check if DSN still matches
if (cached.source === "env") {
const envDsn = detectFromEnv();
if (envDsn?.raw === cached.dsn) {
return envDsn; // Cache valid
}
return envDsn; // DSN changed or removed
}
// For file-based sources, re-read the file
if (cached.sourcePath) {
const content = await Bun.file(cached.sourcePath).text();
const foundDsn = extractDsnFromContent(content, cached.source);
if (foundDsn === cached.dsn) {
return createDetectedDsn(cached.dsn, cached.source, cached.sourcePath);
}
}
return null; // Cache invalid
}
Cache entries are invalidated when the source file changes or the DSN value differs.
Monorepo Support
The CLI detects multiple DSNs in monorepo structures:
my-monorepo/
├── apps/
│ ├── frontend/
│ │ └── .env (DSN 1)
│ └── backend/
│ └── .env (DSN 2)
└── packages/
└── shared/
└── .env (DSN 3)
Use detectAllDsns() to find all DSNs:
// From src/lib/dsn/detector.ts
export async function detectAllDsns(
cwd: string
): Promise<DsnDetectionResult> {
const allDsns: DetectedDsn[] = [];
// 1. Scan all code files
const { dsns: codeDsns } = await scanCodeForDsns(projectRoot);
for (const dsn of codeDsns) {
addDsn(dsn);
}
// 2. Scan all .env files (including monorepo packages)
const { dsns: envFileDsns } = await detectFromAllEnvFiles(projectRoot);
for (const dsn of envFileDsns) {
addDsn(dsn);
}
// 3. Check env var
const envDsn = detectFromEnv();
if (envDsn) {
addDsn(envDsn);
}
return {
primary: allDsns[0] ?? null,
all: allDsns,
hasMultiple: allDsns.length > 1,
fingerprint: createDsnFingerprint(allDsns)
};
}
Each detected DSN includes its packagePath (e.g., apps/frontend) to track which package it belongs to.
Detected DSNs are parsed into components:
type DetectedDsn = {
raw: string; // Full DSN string
publicKey: string; // Public key component
projectId: string; // Numeric project ID
orgId?: string; // Numeric org ID (if present in DSN)
host: string; // Sentry host
source: DsnSource; // "code" | "env_file" | "env"
sourcePath?: string; // Path to source file
packagePath?: string; // Monorepo package path
};
DSN Parsing Example
const dsn = "https://[email protected]/4507034182500352";
const parsed = parseDsn(dsn);
// {
// raw: "https://[email protected]/4507034182500352",
// publicKey: "abc123",
// orgId: "4507034174111744",
// projectId: "4507034182500352",
// host: "o4507034174111744.ingest.us.sentry.io"
// }
Some DSN formats (especially self-hosted) may not include an org ID in the host. In these cases, the CLI resolves the project using the DSN public key via the /api/0/projects?query=dsn:<key> endpoint.
- Fast path (cache hit): ~5ms - reads single file to verify
- Slow path (cache miss): ~2-5s - full glob scan
The caching strategy ensures subsequent commands are nearly instant even in large monorepos.