diff --git a/packages/cli/AGENTS.md b/packages/cli/AGENTS.md index 724f675440..8a2b147b2a 100644 --- a/packages/cli/AGENTS.md +++ b/packages/cli/AGENTS.md @@ -526,7 +526,7 @@ CliError (base, exitCode=1) - Pass `alternatives: []` when defaults are irrelevant (e.g., for missing Trace ID, Event ID) - Use `" and "` in `resource` for plural grammar: `"Trace ID and span ID"` → "are required" -**CI enforcement:** `pnpm run check:errors` scans for `ContextError` with multiline commands, `CliError` with ad-hoc "Try:" strings, and silent `catch` blocks (ratchet baseline — new ones fail CI). +**CI enforcement:** `pnpm run check:errors` scans for `ContextError` with multiline commands and `CliError` with ad-hoc "Try:" strings. Silent `catch` blocks are enforced separately by the `no-silent-catch` Biome plugin (see below). ```typescript // Usage examples @@ -570,14 +570,18 @@ catch (error) { Use `logger.withTag("command-name")` for tagged logging in command files. -**CI enforcement:** `pnpm run check:errors` includes a silent-catch scan that flags -`catch` blocks which are empty, comment-only, or return-only without surfacing the -error. It is enforced with a **ratchet baseline** (`script/silent-catch-baseline.json`) -recording the per-file count of the pre-existing backlog: a *new* silent catch (a file -exceeding its baseline, or one not in the baseline) fails CI, and removing silent -catches without lowering the baseline also fails — so the backlog can only shrink. -When you fix or intentionally add a silent catch, refresh the baseline with -`pnpm run check:errors -- --update` and commit it. +**CI enforcement:** the `no-silent-catch` Biome plugin +(`lint-rules/no-silent-catch.grit`, registered in `biome.jsonc`) flags `catch` +blocks — statement and `.catch()` form — that are empty, comment-only, or +return-only without surfacing the error. The pre-existing backlog is +grandfathered in place with inline +`// biome-ignore lint/plugin: ` comments. Because `pnpm run lint` runs +with `--error-on-warnings`, an *orphaned* suppression (left behind when a +grandfathered catch is fixed) fails as `suppressions/unused` — so the backlog +can only shrink, the same ratchet the old JSON baseline provided, with no +separate script or baseline file to maintain. Fix a grandfathered catch by +adding logging/re-throwing and deleting its `biome-ignore` line; only add a new +suppression for a genuinely intentional silent catch, with a real reason. ### Auto-Recovery for Wrong Entity Types diff --git a/packages/cli/biome.jsonc b/packages/cli/biome.jsonc index 208449a40c..8d695956a8 100644 --- a/packages/cli/biome.jsonc +++ b/packages/cli/biome.jsonc @@ -58,6 +58,16 @@ } }, "overrides": [ + { + // Silent-catch enforcement is scoped to production code under src/**. + // The grandfathered backlog is pinned with inline `// biome-ignore + // lint/plugin` comments; `pnpm run lint` runs with `--error-on-warnings` + // so an orphaned suppression fails as `suppressions/unused`, keeping the + // backlog shrink-only. Replaces the old script/silent-catch-baseline.json + // ratchet (see #1531). + "includes": ["src/**/*.ts"], + "plugins": ["./lint-rules/no-silent-catch.grit"] + }, { // The React-hook lint rules infer "this is a hook" from the // `use*` naming convention. We have a couple of test helpers diff --git a/packages/cli/lint-rules/no-silent-catch.grit b/packages/cli/lint-rules/no-silent-catch.grit index 7d8daa5a95..0066c55b13 100644 --- a/packages/cli/lint-rules/no-silent-catch.grit +++ b/packages/cli/lint-rules/no-silent-catch.grit @@ -7,15 +7,16 @@ language js // covers syntactically empty `catch {}`; this also covers comment-only and // return-only bodies. // -// This replaces the hand-rolled detector in script/check-error-patterns.ts. -// The pre-existing backlog is grandfathered with inline -// `// biome-ignore lint/plugin: ` comments. Removing a grandfathered -// catch orphans its suppression, which Biome reports as `suppressions/unused`; -// the lint step runs with `--error-on-warnings`, so the backlog can only shrink -// (the same ratchet the old baseline JSON provided). +// This replaces the hand-rolled silent-catch detector and the +// script/silent-catch-baseline.json ratchet (see #1531). The pre-existing +// backlog is grandfathered with inline `// biome-ignore lint/plugin: ` +// comments. Removing a grandfathered catch orphans its suppression, which Biome +// reports as `suppressions/unused`; `pnpm run lint` runs with +// `--error-on-warnings`, so the backlog can only shrink (the same ratchet the +// old baseline JSON provided). // -// Registered via an override scoped to `src/**` in biome.jsonc (the old script -// only scanned `src/**/*.ts`). +// Registered via an override scoped to `src/**/*.ts` in biome.jsonc (matching +// the old script's scan scope). or { `try { $t } catch { }`, `try { $t } catch { return; }`, diff --git a/packages/cli/package.json b/packages/cli/package.json index 510b751891..9ceb4cae3b 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -57,7 +57,7 @@ "build:all": "pnpm run generate:schema && pnpm run generate:docs && pnpm run generate:sdk && pnpm tsx script/build.ts", "bundle": "pnpm run generate:schema && pnpm run generate:docs && pnpm run generate:sdk && pnpm tsx script/bundle.ts", "typecheck": "pnpm run generate:docs && pnpm run generate:sdk && tsc --noEmit", - "lint": "biome check --no-errors-on-unmatched --max-diagnostics=none ./", + "lint": "biome check --no-errors-on-unmatched --error-on-warnings --max-diagnostics=none ./", "lint:fix": "biome check --write --no-errors-on-unmatched --max-diagnostics=none ./", "test": "pnpm run test:unit", "test:unit": "pnpm run generate:docs && pnpm run generate:sdk && vitest run test/lib test/commands test/types test/script --coverage", diff --git a/packages/cli/script/check-error-patterns.ts b/packages/cli/script/check-error-patterns.ts index b11ad22631..1691f11cca 100644 --- a/packages/cli/script/check-error-patterns.ts +++ b/packages/cli/script/check-error-patterns.ts @@ -10,48 +10,28 @@ * 2. `new CliError(... "Try:" ...)` — ad-hoc "Try:" strings * → Should use ResolutionError with structured hint/suggestions * - * 3. Silent catch blocks — `catch { ... }` whose body has no logging, no - * re-throw, and at most a bare `return`. Errors must be surfaced via - * `log.debug`/`log.warn` (or re-thrown) per AGENTS.md. Biome's - * `noEmptyBlockStatements` only catches syntactically empty `catch {}`; - * this catches comment-only and return-only blocks too. - * - * Silent catches are enforced with a **ratchet baseline** - * (`silent-catch-baseline.json`): the repo has a pre-existing backlog of - * best-effort catches (UI teardown, cleanup paths, etc.). The baseline records - * the known per-file count so that: - * - a *new* silent catch (a file exceeding its baseline, or a file absent from - * the baseline) fails CI, and - * - removing silent catches without lowering the baseline also fails CI, so - * the backlog can only shrink. - * Run with `--update` to regenerate the baseline after intentionally changing - * the set of silent catches. + * Silent catch blocks used to be checked here via a ratchet baseline. That + * check now lives in the Biome plugin `lint-rules/no-silent-catch.grit`, whose + * grandfathered backlog is pinned inline with `// biome-ignore lint/plugin` + * comments (an unused suppression is itself reported, so the backlog can only + * shrink). See #1531. * * Usage: - * tsx script/check-error-patterns.ts # check (fails CI on drift) - * tsx script/check-error-patterns.ts --update # rewrite the baseline + * tsx script/check-error-patterns.ts # check (fails CI on any violation) * * Exit codes: - * 0 - No anti-patterns found and silent-catch baseline is in sync - * 1 - Anti-patterns detected or silent-catch baseline drifted + * 0 - No anti-patterns found + * 1 - Anti-patterns detected */ -import { readFile, writeFile } from "node:fs/promises"; -import { dirname, join } from "node:path"; -import { fileURLToPath } from "node:url"; +import { readFile } from "node:fs/promises"; import { glob } from "tinyglobby"; export type Violation = { file: string; line: number; message: string }; -/** Per-file count of grandfathered silent catch blocks. */ -export type SilentCatchBaseline = Record; - const CONTEXT_ERROR_RE = /new ContextError\(/g; const TRY_PATTERN_RE = /["'`]Try:/; -const SCRIPT_DIR = dirname(fileURLToPath(import.meta.url)); -export const BASELINE_PATH = join(SCRIPT_DIR, "silent-catch-baseline.json"); - /** Characters that open a nesting level in JavaScript source. */ function isOpener(ch: string): boolean { return ch === "(" || ch === "[" || ch === "{"; @@ -288,191 +268,22 @@ export function findAdHocTryPatterns( return found; } -/** Matches the start of a catch block in both statement and promise form. */ -const CATCH_RE = - /\bcatch\s*(?:\(\s*(\w+)[^)]*\)\s*)?\{|\.catch\(\s*(?:\(\s*(\w+)[^)]*\)|(\w+))\s*=>\s*\{/g; - -/** Tokens inside a catch body that prove the error is surfaced (not silenced). */ -const SURFACING_RE = - /\b(?:log|logger|console)\s*\.|[^.]\bthrow\b|captureException|reportError/; - -/** A catch body consisting solely of a single `return ...;` statement. */ -const RETURN_ONLY_RE = /^return\b[^;]*;?$/; - -/** - * Return the source of a balanced `{...}` block given the index of its opening - * brace, skipping strings so braces inside literals don't break depth tracking. - */ -function readBlock(content: string, openBraceIdx: number): string { - let depth = 0; - let i = openBraceIdx; - while (i < content.length) { - const { next, ch } = advanceToken(content, i); - if (ch === "{") { - depth += 1; - } else if (ch === "}") { - depth -= 1; - if (depth === 0) { - return content.slice(openBraceIdx + 1, i); - } - } - i = next; - } - return content.slice(openBraceIdx + 1); -} - -/** - * Strip line and block comments from a snippet so comment-only catch bodies are - * treated as empty. - */ -function stripComments(snippet: string): string { - return snippet.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\/\/[^\n]*/g, ""); -} - -/** - * Detect silent catch blocks: catch bodies that, after removing comments, are - * empty or contain only a bare `return;`/`return ;` with no logging or - * re-throw. These hide errors and violate the AGENTS.md no-silent-catch rule. - */ -export function findSilentCatches( - content: string, - filePath: string -): Violation[] { - const found: Violation[] = []; - let match = CATCH_RE.exec(content); - while (match !== null) { - const openBraceIdx = match.index + match[0].length - 1; - const errorParam = match[1] ?? match[2] ?? match[3]; - const body = readBlock(content, openBraceIdx); - const code = stripComments(body).trim(); - // A body that references the caught error identifier (forwarding it to a - // handler, attaching it, etc.) is not "silent" even if it lacks an explicit - // log/throw — avoids false positives like `return handleFetchError(error)`. - const usesError = - errorParam !== undefined && new RegExp(`\\b${errorParam}\\b`).test(code); - const returnOnly = RETURN_ONLY_RE.test(code); - const silent = - !(SURFACING_RE.test(code) || usesError) && - (code.length === 0 || returnOnly); - if (silent) { - const line = content.slice(0, match.index).split("\n").length; - found.push({ - file: filePath, - line, - message: - "Silent catch block. Add log.debug()/log.warn() or re-throw — errors must not vanish (AGENTS.md).", - }); - } - match = CATCH_RE.exec(content); - } - return found; -} - -export type ScanResult = { - /** Hard violations — always fail CI. */ - violations: Violation[]; - /** Every silent catch found, across all scanned files. */ - silentCatches: Violation[]; -}; - -/** Scan the given files and collect violations and silent catches. */ -export async function scanFiles(files: string[]): Promise { +/** Scan the given files and collect violations. */ +export async function scanFiles(files: string[]): Promise { const violations: Violation[] = []; - const silentCatches: Violation[] = []; for (const filePath of files) { const content = await readFile(filePath, "utf-8"); violations.push(...findContextErrorNewlines(content, filePath)); violations.push(...findAdHocTryPatterns(content, filePath)); - silentCatches.push(...findSilentCatches(content, filePath)); } - return { violations, silentCatches }; -} - -/** Group silent catches into a per-file count map. */ -export function countByFile(silentCatches: Violation[]): SilentCatchBaseline { - const counts: SilentCatchBaseline = {}; - for (const v of silentCatches) { - counts[v.file] = (counts[v.file] ?? 0) + 1; - } - return counts; -} - -export type BaselineDrift = { - /** Files with more silent catches than the baseline allows (or new files). */ - regressions: { file: string; baseline: number; actual: number }[]; - /** Files with fewer silent catches than the baseline records. */ - improvements: { file: string; baseline: number; actual: number }[]; -}; - -/** - * Compare the current per-file silent-catch counts against the committed - * baseline. A regression (new silent catch) always fails CI. An improvement - * (silent catch removed without updating the baseline) also fails so the - * baseline stays honest and can only ratchet down. - */ -export function compareToBaseline( - actual: SilentCatchBaseline, - baseline: SilentCatchBaseline -): BaselineDrift { - const regressions: BaselineDrift["regressions"] = []; - const improvements: BaselineDrift["improvements"] = []; - const files = new Set([...Object.keys(actual), ...Object.keys(baseline)]); - for (const file of files) { - const a = actual[file] ?? 0; - const b = baseline[file] ?? 0; - if (a > b) { - regressions.push({ file, baseline: b, actual: a }); - } else if (a < b) { - improvements.push({ file, baseline: b, actual: a }); - } - } - regressions.sort((x, y) => x.file.localeCompare(y.file)); - improvements.sort((x, y) => x.file.localeCompare(y.file)); - return { regressions, improvements }; -} - -/** Load the committed baseline, treating a missing file as an empty baseline. */ -async function loadBaseline(): Promise { - try { - return JSON.parse(await readFile(BASELINE_PATH, "utf-8")); - } catch (error) { - if ((error as NodeJS.ErrnoException).code === "ENOENT") { - return {}; - } - throw error; - } -} - -/** Serialize the baseline with stable key ordering and a trailing newline. */ -function serializeBaseline(counts: SilentCatchBaseline): string { - const sorted: SilentCatchBaseline = {}; - for (const key of Object.keys(counts).sort()) { - sorted[key] = counts[key] as number; - } - return `${JSON.stringify(sorted, null, 2)}\n`; + return violations; } async function main(): Promise { - const update = process.argv.includes("--update"); const files = await glob("src/**/*.ts"); - const { violations, silentCatches } = await scanFiles(files); - const actual = countByFile(silentCatches); - - if (update) { - await writeFile(BASELINE_PATH, serializeBaseline(actual)); - const total = silentCatches.length; - console.log( - `✓ Wrote silent-catch baseline: ${total} catch(es) across ${Object.keys(actual).length} file(s).` - ); - } - - const baseline = update ? actual : await loadBaseline(); - const { regressions, improvements } = compareToBaseline(actual, baseline); - - let failed = false; + const violations = await scanFiles(files); if (violations.length > 0) { - failed = true; console.error( `✗ Found ${violations.length} error class anti-pattern(s):\n` ); @@ -486,51 +297,10 @@ async function main(): Promise { console.error( "See ContextError JSDoc in src/lib/errors.ts for usage guidance.\n" ); - } - - if (regressions.length > 0) { - failed = true; - const added = regressions.reduce((n, r) => n + (r.actual - r.baseline), 0); - console.error( - `✗ ${added} new silent catch block(s) beyond the baseline:\n` - ); - for (const r of regressions) { - console.error(` ${r.file}: ${r.baseline} → ${r.actual}`); - } - console.error( - "\nEvery catch must re-throw, log.debug()/log.warn(), or return a fallback " + - "with a log.debug() explaining the suppression (AGENTS.md)." - ); - console.error( - "If a silent catch is truly intentional, run `pnpm run check:errors -- --update`.\n" - ); - } - - if (improvements.length > 0) { - failed = true; - const removed = improvements.reduce( - (n, r) => n + (r.baseline - r.actual), - 0 - ); - console.error( - `✗ ${removed} silent catch block(s) removed but the baseline is stale:\n` - ); - for (const r of improvements) { - console.error(` ${r.file}: ${r.baseline} → ${r.actual}`); - } - console.error( - "\nNice — the backlog shrank. Lock it in with `pnpm run check:errors -- --update`.\n" - ); - } - - if (failed) { process.exit(1); } - const total = silentCatches.length; - console.log( - `✓ No error class anti-patterns found (silent-catch baseline: ${total} grandfathered).` - ); + console.log("✓ No error class anti-patterns found."); process.exit(0); } diff --git a/packages/cli/script/silent-catch-baseline.json b/packages/cli/script/silent-catch-baseline.json deleted file mode 100644 index ba386dc90c..0000000000 --- a/packages/cli/script/silent-catch-baseline.json +++ /dev/null @@ -1,86 +0,0 @@ -{ - "src/cli.ts": 2, - "src/commands/api.ts": 4, - "src/commands/auth/login.ts": 2, - "src/commands/auth/whoami.ts": 1, - "src/commands/cli/fix.ts": 1, - "src/commands/cli/setup.ts": 1, - "src/commands/dashboard/create.ts": 1, - "src/commands/issue/resolve-commit-spec.ts": 1, - "src/commands/org/list.ts": 1, - "src/commands/project/delete.ts": 1, - "src/commands/release/set-commits.ts": 2, - "src/commands/snapshots/diff.ts": 1, - "src/lib/api/projects.ts": 4, - "src/lib/binary.ts": 2, - "src/lib/browser.ts": 1, - "src/lib/cache-keys.ts": 1, - "src/lib/clipboard.ts": 1, - "src/lib/constants.ts": 1, - "src/lib/custom-ca.ts": 1, - "src/lib/db/auth.ts": 5, - "src/lib/db/dsn-cache.ts": 3, - "src/lib/db/index.ts": 2, - "src/lib/db/migration.ts": 1, - "src/lib/db/project-root-cache.ts": 1, - "src/lib/db/regions.ts": 1, - "src/lib/db/schema.ts": 2, - "src/lib/db/sqlite.ts": 1, - "src/lib/delta-upgrade.ts": 1, - "src/lib/detect-agent.ts": 2, - "src/lib/dev-script.ts": 2, - "src/lib/dif/find.ts": 0, - "src/lib/docs-context.ts": 4, - "src/lib/docs-service.ts": 1, - "src/lib/dsn/detector.ts": 1, - "src/lib/dsn/errors.ts": 1, - "src/lib/dsn/parser.ts": 2, - "src/lib/dsn/project-root.ts": 3, - "src/lib/dsn/resolver.ts": 1, - "src/lib/error-reporting.ts": 2, - "src/lib/errors.ts": 1, - "src/lib/formatters/conversation.ts": 1, - "src/lib/formatters/markdown.ts": 1, - "src/lib/formatters/sql.ts": 2, - "src/lib/git.ts": 10, - "src/lib/hex-id-recovery.ts": 1, - "src/lib/init/preflight.ts": 3, - "src/lib/init/stdin-reopen.ts": 3, - "src/lib/init/tools/file-changes/prepare.ts": 4, - "src/lib/init/tools/file-exists-batch.ts": 1, - "src/lib/init/tools/list-dir.ts": 3, - "src/lib/init/tools/read-files.ts": 1, - "src/lib/init/tools/shared.ts": 2, - "src/lib/init/ui/ink-ui.ts": 8, - "src/lib/init/verify-setup.ts": 1, - "src/lib/init/wizard-runner.ts": 1, - "src/lib/init/workflow-inputs.ts": 1, - "src/lib/logger.ts": 1, - "src/lib/oauth.ts": 1, - "src/lib/progress.ts": 1, - "src/lib/react-native/wrap-call.ts": 2, - "src/lib/region.ts": 2, - "src/lib/resolve-target.ts": 5, - "src/lib/resolve-team.ts": 1, - "src/lib/response-cache.ts": 7, - "src/lib/scan/worker-pool.ts": 1, - "src/lib/scope-recovery.ts": 0, - "src/lib/sdk-invoke.ts": 2, - "src/lib/search-query.ts": 1, - "src/lib/sentry-client.ts": 1, - "src/lib/sentry-url-parser.ts": 1, - "src/lib/sentry-urls.ts": 4, - "src/lib/shell.ts": 2, - "src/lib/sixel.ts": 3, - "src/lib/sourcemap/debug-id.ts": 1, - "src/lib/telemetry.ts": 6, - "src/lib/telemetry/zstd-transport.ts": 1, - "src/lib/timezone.ts": 4, - "src/lib/token-claims.ts": 1, - "src/lib/trace-target.ts": 1, - "src/lib/upgrade.ts": 3, - "src/lib/utils.ts": 1, - "src/lib/version-check.ts": 0, - "src/lib/which.ts": 1, - "src/lib/wrangler.ts": 1 -} diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 9c899dc82b..522628cc47 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -50,6 +50,7 @@ async function preloadProjectContext(cwd: string): Promise { // Apply persistent URL default (lower priority than env vars and .sentryclirc). const env = getEnv(); if (!(env.SENTRY_HOST?.trim() || env.SENTRY_URL?.trim())) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { getDefaultUrl } = await import("./lib/db/defaults.js"); const url = getDefaultUrl(); @@ -501,6 +502,7 @@ export async function runCli(cliArgs: string[]): Promise { return; } // Best-effort: telemetry must never crash the CLI + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await reportUnknownCommand(argv); } catch { diff --git a/packages/cli/src/commands/api.ts b/packages/cli/src/commands/api.ts index 11803f751d..8a6bae8574 100644 --- a/packages/cli/src/commands/api.ts +++ b/packages/cli/src/commands/api.ts @@ -143,6 +143,7 @@ export function normalizeEndpoint(endpoint: string): string { * @internal Exported for testing */ export function parseFieldValue(value: string): unknown { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.parse(value); } catch { @@ -670,6 +671,7 @@ export function parseHeaders(headers: string[]): Record { export function parseDataBody( data: string ): Record | unknown[] | string { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.parse(data) as Record | unknown[]; } catch { @@ -761,6 +763,7 @@ function tryParseJsonField( return; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.parse(field) as Record | unknown[]; } catch { @@ -848,6 +851,7 @@ export async function buildBodyFromInput( } // Try to parse as JSON for the API client + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.parse(content) as Record; } catch { diff --git a/packages/cli/src/commands/auth/login.ts b/packages/cli/src/commands/auth/login.ts index a2621886c4..0e66c857be 100644 --- a/packages/cli/src/commands/auth/login.ts +++ b/packages/cli/src/commands/auth/login.ts @@ -483,6 +483,7 @@ export const loginCommand = buildCommand({ } } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await clearResponseCache(); } catch { @@ -509,6 +510,7 @@ export const loginCommand = buildCommand({ method: "token", configPath: getDbPath(), }; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const user = await getCurrentUser(); setUserInfo({ @@ -556,6 +558,7 @@ export const loginCommand = buildCommand({ * on the next command that needs it. */ function warmOrgCache(): void { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. listOrganizationsUncached().catch(() => { // Best-effort: cache warming failure doesn't affect the login result }); diff --git a/packages/cli/src/commands/auth/whoami.ts b/packages/cli/src/commands/auth/whoami.ts index 1671658436..525f5d7b31 100644 --- a/packages/cli/src/commands/auth/whoami.ts +++ b/packages/cli/src/commands/auth/whoami.ts @@ -106,6 +106,7 @@ export const whoamiCommand = buildCommand({ // Keep cached user info up to date. Non-fatal: display must succeed even // if the DB write fails (read-only filesystem, corrupted database, etc.). + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { setUserInfo({ userId: user.id, diff --git a/packages/cli/src/commands/cli/fix.ts b/packages/cli/src/commands/cli/fix.ts index 99d8ed008a..1b3083255c 100644 --- a/packages/cli/src/commands/cli/fix.ts +++ b/packages/cli/src/commands/cli/fix.ts @@ -252,6 +252,7 @@ async function checkOwnership( * separate argument — the shell never interpolates it, preventing injection. */ function resolveUid(username: string): number | null { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const result = execFileSync("id", ["-u", "--", username], { encoding: "utf-8", diff --git a/packages/cli/src/commands/cli/setup.ts b/packages/cli/src/commands/cli/setup.ts index 7a5ce6b0ee..5288be295b 100644 --- a/packages/cli/src/commands/cli/setup.ts +++ b/packages/cli/src/commands/cli/setup.ts @@ -111,6 +111,7 @@ async function handleInstall( // Clean up temp binary (Posix only — the inode stays alive for the running process) if (process.platform !== "win32") { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { unlinkSync(execPath); } catch { diff --git a/packages/cli/src/commands/dashboard/create.ts b/packages/cli/src/commands/dashboard/create.ts index 8ca5be6f82..85f54f0336 100644 --- a/packages/cli/src/commands/dashboard/create.ts +++ b/packages/cli/src/commands/dashboard/create.ts @@ -69,6 +69,7 @@ async function enrichTargetProjectIds( if (t.projectId !== undefined) { return t.projectId; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const info = await getProject(t.org, t.project); return toNumericId(info.id); diff --git a/packages/cli/src/commands/dashboard/resolve.ts b/packages/cli/src/commands/dashboard/resolve.ts index d86e44eb11..dc9367f4b8 100644 --- a/packages/cli/src/commands/dashboard/resolve.ts +++ b/packages/cli/src/commands/dashboard/resolve.ts @@ -696,6 +696,7 @@ async function build404Error( "Check the dashboard ID or title with: sentry dashboard list", ]; if (ctx.orgSlug) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { data } = await listDashboardsPaginated(ctx.orgSlug, { perPage: MAX_404_SUGGESTIONS, diff --git a/packages/cli/src/commands/issue/resolve-commit-spec.ts b/packages/cli/src/commands/issue/resolve-commit-spec.ts index 3cd7f701ee..f53533f253 100644 --- a/packages/cli/src/commands/issue/resolve-commit-spec.ts +++ b/packages/cli/src/commands/issue/resolve-commit-spec.ts @@ -29,6 +29,7 @@ import type { SentryRepository } from "../../types/index.js"; /** Fetch the git origin URL without throwing when it's missing. */ function getGitOriginUrl(cwd: string): string | undefined { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return execFileSync("git", ["remote", "get-url", "origin"], { cwd, diff --git a/packages/cli/src/commands/monitor/run.ts b/packages/cli/src/commands/monitor/run.ts index db3582e9ec..b1a028c05b 100644 --- a/packages/cli/src/commands/monitor/run.ts +++ b/packages/cli/src/commands/monitor/run.ts @@ -116,6 +116,7 @@ async function sendCheckInSafely( const send = sendEnvelopeRequest(dsn, body); // Prevent unhandled rejection if the timeout wins the race but the // fetch later rejects (Node 15+ terminates on unhandled rejections). + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. send.catch(() => { // Intentionally empty — prevents unhandled rejection if timeout wins. }); diff --git a/packages/cli/src/commands/org/list.ts b/packages/cli/src/commands/org/list.ts index 75388d34f2..c4eb563956 100644 --- a/packages/cli/src/commands/org/list.ts +++ b/packages/cli/src/commands/org/list.ts @@ -47,6 +47,7 @@ type OrgListEntry = SentryOrganization & { region?: string }; * @example "https://east-1.us.sentry.io" -> "EAST-1.US" */ function getRegionDisplayName(regionUrl: string): string { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const url = new URL(regionUrl); const { hostname } = url; diff --git a/packages/cli/src/commands/project/delete.ts b/packages/cli/src/commands/project/delete.ts index 0d6cacf505..c4549ca1ea 100644 --- a/packages/cli/src/commands/project/delete.ts +++ b/packages/cli/src/commands/project/delete.ts @@ -70,6 +70,7 @@ async function buildPermissionError( // org listing has already been fetched during this session. let orgRole = getCachedOrgRole(orgSlug); if (!orgRole) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const org = await getOrganization(orgSlug); orgRole = (org as Record).orgRole as string | undefined; diff --git a/packages/cli/src/commands/release/set-commits.ts b/packages/cli/src/commands/release/set-commits.ts index f5044dd804..0cfd647d0f 100644 --- a/packages/cli/src/commands/release/set-commits.ts +++ b/packages/cli/src/commands/release/set-commits.ts @@ -108,6 +108,7 @@ const REPO_CACHE_TTL_MS = 60 * 60 * 1000; /** Check if we've cached that this org has no repo integration */ function hasNoRepoIntegration(orgSlug: string): boolean { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const db = getDatabase(); const key = `repos_configured.${orgSlug}`; @@ -127,6 +128,7 @@ function hasNoRepoIntegration(orgSlug: string): boolean { /** Cache that this org has no repo integration */ function cacheNoRepoIntegration(orgSlug: string): void { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const db = getDatabase(); const key = `repos_configured.${orgSlug}`; @@ -141,6 +143,7 @@ function cacheNoRepoIntegration(orgSlug: string): void { /** Clear the negative cache (e.g., when auto succeeds) */ function clearRepoIntegrationCache(orgSlug: string): void { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const db = getDatabase(); const key = `repos_configured.${orgSlug}`; diff --git a/packages/cli/src/commands/snapshots/diff.ts b/packages/cli/src/commands/snapshots/diff.ts index e653b825b1..cb8898716c 100644 --- a/packages/cli/src/commands/snapshots/diff.ts +++ b/packages/cli/src/commands/snapshots/diff.ts @@ -96,6 +96,7 @@ function errorMessage(err: unknown): string { /** Assert a path is an existing directory. */ function assertDirectory(path: string, label: string): void { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { if (statSync(path).isDirectory()) { return; diff --git a/packages/cli/src/lib/api/projects.ts b/packages/cli/src/lib/api/projects.ts index 65085d5e2a..dd4c28adc7 100644 --- a/packages/cli/src/lib/api/projects.ts +++ b/packages/cli/src/lib/api/projects.ts @@ -73,6 +73,7 @@ export async function listProjects(orgSlug: string): Promise { // Populate project cache for shell completions (best-effort). // Mirrors how listOrganizations() calls setOrgRegions(). + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const orgs = getCachedOrganizations(); const orgName = orgs.find((o) => o.slug === orgSlug)?.name ?? orgSlug; @@ -173,6 +174,7 @@ function seedProjectCaches( project: SentryProject, dsn: string | null ): void { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const orgName = resolveOrgDisplayName(orgSlug, project.organization?.name); cacheProjectsForOrg(orgSlug, orgName, [ @@ -182,6 +184,7 @@ function seedProjectCaches( // Best-effort — don't let cache failures break project creation } if (dsn) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const publicKey = extractPublicKeyFromDsn(dsn); if (publicKey) { @@ -204,6 +207,7 @@ function seedProjectCaches( * DSN format: https://@/ */ function extractPublicKeyFromDsn(dsn: string): string | null { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const url = new URL(dsn); return url.username || null; @@ -500,6 +504,7 @@ export async function findProjectByDsnKey( const results = await Promise.all( regions.map((region) => limit(async () => { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { // Same `?query=dsn:` escape hatch as above (see the region-fallback // branch) — internal search param, no typed SDK operation yet. @@ -627,6 +632,7 @@ export async function tryGetPrimaryDsn( orgSlug: string, projectSlug: string ): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const keys = await getProjectKeys(orgSlug, projectSlug); const activeKey = keys.find((k) => k.isActive); diff --git a/packages/cli/src/lib/api/releases.ts b/packages/cli/src/lib/api/releases.ts index f05758a330..c9996f7c9c 100644 --- a/packages/cli/src/lib/api/releases.ts +++ b/packages/cli/src/lib/api/releases.ts @@ -381,6 +381,7 @@ async function getPreviousReleaseCommit( orgSlug: string, version: string ): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const regionUrl = await resolveOrgRegion(orgSlug); const encodedVersion = encodeURIComponent(version); diff --git a/packages/cli/src/lib/api/sourcemaps.ts b/packages/cli/src/lib/api/sourcemaps.ts index ce3905db1f..a30c82473a 100644 --- a/packages/cli/src/lib/api/sourcemaps.ts +++ b/packages/cli/src/lib/api/sourcemaps.ts @@ -279,6 +279,7 @@ export async function uploadSourcemaps(options: UploadOptions): Promise { }); } finally { // Always clean up the temp file + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await unlink(tmpZipPath).catch(() => { // Best-effort cleanup — OS temp directory will eventually purge it }); diff --git a/packages/cli/src/lib/binary.ts b/packages/cli/src/lib/binary.ts index 215d18669f..89717e8f1c 100644 --- a/packages/cli/src/lib/binary.ts +++ b/packages/cli/src/lib/binary.ts @@ -326,6 +326,7 @@ export function replaceBinarySync(tempPath: string, installPath: string): void { renameSync(installPath, oldPath); } catch { // Current binary might not exist (fresh install) or .old already exists + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { unlinkSync(oldPath); renameSync(installPath, oldPath); @@ -353,6 +354,7 @@ export function replaceBinarySync(tempPath: string, installPath: string): void { */ export function cleanupOldBinary(oldPath: string): void { // Fire-and-forget: don't await, just let cleanup run in background + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. unlink(oldPath).catch(() => { // Intentionally ignore errors — file may not exist }); @@ -452,6 +454,7 @@ function handleExistingLock(lockPath: string): void { * @param lockPath - Path to the lock file */ export function releaseLock(lockPath: string): void { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { unlinkSync(lockPath); } catch { diff --git a/packages/cli/src/lib/browser.ts b/packages/cli/src/lib/browser.ts index 9b940c065b..8e200aa8a6 100644 --- a/packages/cli/src/lib/browser.ts +++ b/packages/cli/src/lib/browser.ts @@ -55,6 +55,7 @@ export async function openBrowser(url: string): Promise { return false; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const proc = spawn(command, args, { stdio: ["ignore", "ignore", "ignore"], diff --git a/packages/cli/src/lib/cache-keys.ts b/packages/cli/src/lib/cache-keys.ts index e9c7bb9124..41a7f8c294 100644 --- a/packages/cli/src/lib/cache-keys.ts +++ b/packages/cli/src/lib/cache-keys.ts @@ -104,6 +104,7 @@ export function computeInvalidationPrefixes( apiBaseUrl: string ): string[] { let parsed: URL; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { parsed = new URL(fullUrl); } catch { diff --git a/packages/cli/src/lib/clipboard.ts b/packages/cli/src/lib/clipboard.ts index e4ed2460b7..aa41c9b11a 100644 --- a/packages/cli/src/lib/clipboard.ts +++ b/packages/cli/src/lib/clipboard.ts @@ -53,6 +53,7 @@ export async function copyToClipboard(text: string): Promise { return false; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const proc = spawn(command, args, { stdio: ["pipe", "ignore", "ignore"], diff --git a/packages/cli/src/lib/close-dispatcher.ts b/packages/cli/src/lib/close-dispatcher.ts index 9c04ddd885..63c1a1a398 100644 --- a/packages/cli/src/lib/close-dispatcher.ts +++ b/packages/cli/src/lib/close-dispatcher.ts @@ -22,6 +22,7 @@ export async function closeGlobalDispatcher(): Promise { GLOBAL_DISPATCHER ] as ClosableDispatcher | undefined; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await dispatcher?.destroy?.(); } catch { diff --git a/packages/cli/src/lib/constants.ts b/packages/cli/src/lib/constants.ts index 4e707773af..4ad4113016 100644 --- a/packages/cli/src/lib/constants.ts +++ b/packages/cli/src/lib/constants.ts @@ -140,6 +140,7 @@ export const SENTRY_CLI_DSN = * mechanism directly. */ if (typeof __SENTRY_DEBUG_ID__ !== "undefined") { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { // biome-ignore lint/suspicious/useErrorMessage: stack trace capture only const stack = new Error().stack; diff --git a/packages/cli/src/lib/db/auth.ts b/packages/cli/src/lib/db/auth.ts index 2b78a39c51..df2164508d 100644 --- a/packages/cli/src/lib/db/auth.ts +++ b/packages/cli/src/lib/db/auth.ts @@ -60,6 +60,7 @@ function migrateNullHost(row: AuthRow): string { const bootHost = getEnvTokenHost(); const migratedHost = normalizeOrigin(bootHost); const host = migratedHost ?? DEFAULT_SENTRY_URL; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { withDbSpan("migrateAuthHost", () => { const db = getDatabase(); @@ -214,6 +215,7 @@ export function getAuthConfig(): AuthConfig | undefined { * falls back to the caller's default behavior. */ export function getStoredAuthHost(): string | undefined { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return withDbSpan("getStoredAuthHost", () => { const row = getAuthRow(); @@ -241,6 +243,7 @@ export function getStoredAuthHost(): string | undefined { * OAuth's host over the env-token snapshot. */ export function hasUsableStoredToken(): boolean { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return withDbSpan("hasUsableStoredToken", () => { const row = getAuthRow(); @@ -270,6 +273,7 @@ export function hasUsableStoredToken(): boolean { * first access, same as {@link getStoredAuthHost}). */ export function getUsableStoredTokenHost(): string | undefined { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return withDbSpan("getUsableStoredTokenHost", () => { const row = getAuthRow(); @@ -456,6 +460,7 @@ export async function clearAuth(): Promise { clearTrustedHostState(); // Dynamic import avoids the auth→response-cache→auth cycle. + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { clearResponseCache } = await import("../response-cache.js"); await clearResponseCache(); diff --git a/packages/cli/src/lib/db/dsn-cache.ts b/packages/cli/src/lib/db/dsn-cache.ts index deb5cbdfc6..c1ef07f661 100644 --- a/packages/cli/src/lib/db/dsn-cache.ts +++ b/packages/cli/src/lib/db/dsn-cache.ts @@ -119,6 +119,7 @@ function rowToCachedDsnEntry(row: DsnCacheRow): CachedDsnEntry { // Parse allResolved from all_dsns_json for inferred sources if (row.source === "inferred" && row.all_dsns_json) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { entry.allResolved = JSON.parse( row.all_dsns_json @@ -240,6 +241,7 @@ async function validateDirMtime( fullPath: string, cachedMtime: number ): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const stats = await stat(fullPath); return Math.floor(stats.mtimeMs) === cachedMtime; @@ -256,6 +258,7 @@ async function validateFileMtime( fullPath: string, cachedMtime: number ): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const stats = await stat(fullPath); if (!stats.isFile()) { diff --git a/packages/cli/src/lib/db/index.ts b/packages/cli/src/lib/db/index.ts index 46cfc7ca1f..7aa4af95da 100644 --- a/packages/cli/src/lib/db/index.ts +++ b/packages/cli/src/lib/db/index.ts @@ -85,15 +85,18 @@ function ensureConfigDir(): void { function setDbPermissions(): void { const dbPath = getDbPath(); + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { chmodSync(dbPath, 0o600); // WAL mode creates -wal and -shm files that may contain sensitive data // Chmod them too if they exist (they may not exist on first run) + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { chmodSync(`${dbPath}-wal`, 0o600); } catch { // File may not exist yet } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { chmodSync(`${dbPath}-shm`, 0o600); } catch { diff --git a/packages/cli/src/lib/db/migration.ts b/packages/cli/src/lib/db/migration.ts index 16ce9c5935..380ac3f2a0 100644 --- a/packages/cli/src/lib/db/migration.ts +++ b/packages/cli/src/lib/db/migration.ts @@ -44,6 +44,7 @@ function oldConfigExists(): boolean { function readOldConfig(): OldConfig | null { const configPath = join(getConfigDir(), OLD_CONFIG_FILENAME); + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { readFileSync } = _require("node:fs"); const content = readFileSync(configPath, "utf-8"); diff --git a/packages/cli/src/lib/db/project-root-cache.ts b/packages/cli/src/lib/db/project-root-cache.ts index aad8a9bded..a3329ea6b9 100644 --- a/packages/cli/src/lib/db/project-root-cache.ts +++ b/packages/cli/src/lib/db/project-root-cache.ts @@ -113,6 +113,7 @@ export async function setCachedProjectRoot( // Get current mtime of the cwd directory let cwdMtime: number; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const stats = await stat(cwd); cwdMtime = Math.floor(stats.mtimeMs); diff --git a/packages/cli/src/lib/db/regions.ts b/packages/cli/src/lib/db/regions.ts index 2cbf376bc4..8756328c88 100644 --- a/packages/cli/src/lib/db/regions.ts +++ b/packages/cli/src/lib/db/regions.ts @@ -36,6 +36,7 @@ function seedTrustedRegionOriginsIfNeeded(): void { return; } trustedRegionOriginsSeeded = true; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const db = getDatabase(); const rows = db diff --git a/packages/cli/src/lib/db/schema.ts b/packages/cli/src/lib/db/schema.ts index 847fdfdf0a..ed13e44ca7 100644 --- a/packages/cli/src/lib/db/schema.ts +++ b/packages/cli/src/lib/db/schema.ts @@ -576,6 +576,7 @@ export function repairSchema(db: Database): RepairResult { repairWrongPrimaryKeys(db, result); if (result.fixed.length > 0) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { db.query("UPDATE schema_version SET version = ?").run( CURRENT_SCHEMA_VERSION @@ -678,6 +679,7 @@ export function tryRepairAndRetry( isRepairing = true; let repairSucceeded = false; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { // Dynamic imports to avoid circular dependencies with db/index.js const { getRawDatabase } = _require("./index.js") as { diff --git a/packages/cli/src/lib/db/sqlite.ts b/packages/cli/src/lib/db/sqlite.ts index 0360175a72..efbc2880b5 100644 --- a/packages/cli/src/lib/db/sqlite.ts +++ b/packages/cli/src/lib/db/sqlite.ts @@ -389,6 +389,7 @@ function writeLockOwner(dbPath: string): void { /** Read the recorded owner PID, or null if absent/unreadable. */ function readLockOwner(dbPath: string): number | null { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const pid = Number.parseInt( readFileSync(lockOwnerPath(dbPath), "utf8"), diff --git a/packages/cli/src/lib/delta-upgrade.ts b/packages/cli/src/lib/delta-upgrade.ts index 0b7c7c7095..ec709b8c8d 100644 --- a/packages/cli/src/lib/delta-upgrade.ts +++ b/packages/cli/src/lib/delta-upgrade.ts @@ -302,6 +302,7 @@ export async function resolveNightlyChain(opts: { } let manifests: OciManifest[]; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { manifests = await Promise.all( chainTags.map((tag) => client.fetchManifest(opts.token, tag, opts.signal)) diff --git a/packages/cli/src/lib/detect-agent.ts b/packages/cli/src/lib/detect-agent.ts index 5efbf934b6..f1cf18ab76 100644 --- a/packages/cli/src/lib/detect-agent.ts +++ b/packages/cli/src/lib/detect-agent.ts @@ -290,6 +290,7 @@ export async function getProcessInfoFromOS( pid: number ): Promise { // Linux: /proc is an in-memory filesystem — fast even though async + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const status = await readFile(`/proc/${pid}/status`, "utf-8"); const nameMatch = status.match(PROC_STATUS_NAME_RE); @@ -303,6 +304,7 @@ export async function getProcessInfoFromOS( // macOS / other Unix: use ps(1) asynchronously if (process.platform !== "win32") { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const result = await execFileUnreffed( "ps", diff --git a/packages/cli/src/lib/dev-script.ts b/packages/cli/src/lib/dev-script.ts index d17ea905e1..bbb9fd2346 100644 --- a/packages/cli/src/lib/dev-script.ts +++ b/packages/cli/src/lib/dev-script.ts @@ -102,6 +102,7 @@ async function tryPythonFile( filename: string, args: string[] ): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await access(join(cwd, filename)); return { args, source: filename }; @@ -112,6 +113,7 @@ async function tryPythonFile( /** Check for go.mod and return `go run .` */ async function tryGoMod(cwd: string): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await access(join(cwd, "go.mod")); return { args: ["go", "run", "."], source: "go.mod" }; @@ -123,6 +125,7 @@ async function tryGoMod(cwd: string): Promise { /** Check for docker-compose.yml or compose.yml. */ async function tryDockerCompose(cwd: string): Promise { for (const filename of ["docker-compose.yml", "compose.yml"]) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await access(join(cwd, filename)); return { args: ["docker", "compose", "up"], source: filename }; diff --git a/packages/cli/src/lib/docs-context.ts b/packages/cli/src/lib/docs-context.ts index 77815aa3a8..6f54bd1721 100644 --- a/packages/cli/src/lib/docs-context.ts +++ b/packages/cli/src/lib/docs-context.ts @@ -105,6 +105,7 @@ export async function detectDocsContextFromReader( let sentryConfigured = false; for (const manifest of DOCS_CONTEXT_MANIFESTS) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const contents = await reader.readManifest(manifest); if (contents === undefined) { @@ -119,6 +120,7 @@ export async function detectDocsContextFromReader( } for (const config of DOCS_CONTEXT_CONFIGS) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { sentryConfigured ||= await reader.hasConfig(config); } catch { @@ -137,6 +139,7 @@ export async function detectDocsContextFromReader( export function detectDocsContext(cwd: string): Promise { return detectDocsContextFromReader({ async readManifest(name) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return await readFile(join(cwd, name), "utf8"); } catch { @@ -144,6 +147,7 @@ export function detectDocsContext(cwd: string): Promise { } }, async hasConfig(name) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await access(join(cwd, name)); return true; diff --git a/packages/cli/src/lib/docs-service.ts b/packages/cli/src/lib/docs-service.ts index b2b7eb53ef..2acd899fb0 100644 --- a/packages/cli/src/lib/docs-service.ts +++ b/packages/cli/src/lib/docs-service.ts @@ -35,6 +35,7 @@ async function postDocs( if (!response.ok) { let detail = text; let parsed: { code?: unknown; error?: unknown } | undefined; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { parsed = JSON.parse(text) as { code?: unknown; error?: unknown }; } catch { diff --git a/packages/cli/src/lib/dsn/detector.ts b/packages/cli/src/lib/dsn/detector.ts index aa8bf590d3..6c4253e82a 100644 --- a/packages/cli/src/lib/dsn/detector.ts +++ b/packages/cli/src/lib/dsn/detector.ts @@ -204,6 +204,7 @@ export async function detectAllDsns(cwd: string): Promise { // Get project root directory mtime for quick invalidation // when files are added/removed at root level let rootDirMtime = 0; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const stats = await stat(projectRoot); rootDirMtime = Math.floor(stats.mtimeMs); @@ -284,6 +285,7 @@ async function verifyFileDsnCache( const filePath = join(cwd, cached.sourcePath); + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { // Guard: skip non-regular files (FIFOs, sockets, etc.) that would block. // 1Password streams secrets via symlinked named pipes; Bun.file().text() diff --git a/packages/cli/src/lib/dsn/env-file.ts b/packages/cli/src/lib/dsn/env-file.ts index a1fee3a60e..3b518c6de9 100644 --- a/packages/cli/src/lib/dsn/env-file.ts +++ b/packages/cli/src/lib/dsn/env-file.ts @@ -210,6 +210,7 @@ export async function detectFromMonorepoEnvFiles( // surfaces when iterating. Wrap the full open+iterate in one try/catch. // No explicit handle.close() needed: for-await-of auto-closes the Dir // handle when the loop exits (including early return or break). + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { for await (const entry of await opendir(rootDir)) { // Skip hidden dirs (.git, .cache) and non-directories. Accept diff --git a/packages/cli/src/lib/dsn/errors.ts b/packages/cli/src/lib/dsn/errors.ts index 19cf4c4fc5..9b91ff8710 100644 --- a/packages/cli/src/lib/dsn/errors.ts +++ b/packages/cli/src/lib/dsn/errors.ts @@ -65,6 +65,7 @@ export async function formatNoDsnError( // Try to fetch and show accessible projects if (showProjects) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const projects = await getAccessibleProjects(); diff --git a/packages/cli/src/lib/dsn/parser.ts b/packages/cli/src/lib/dsn/parser.ts index 4059aa4a72..1f25406ca1 100644 --- a/packages/cli/src/lib/dsn/parser.ts +++ b/packages/cli/src/lib/dsn/parser.ts @@ -77,6 +77,7 @@ export function isPlaceholderNumericId(id: string): boolean { export function isPlaceholderPublicKey(publicKey: string): boolean { // URL parsers may percent-encode angle-bracket templates such as `%3Ckey%3E`. let decoded = publicKey; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { decoded = decodeURIComponent(publicKey); } catch { @@ -102,6 +103,7 @@ export function isPlaceholderPublicKey(publicKey: string): boolean { * // } */ export function parseDsn(dsn: string): ParsedDsn | null { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const url = new URL(dsn); diff --git a/packages/cli/src/lib/dsn/project-root.ts b/packages/cli/src/lib/dsn/project-root.ts index 11bf454d91..86123db1b1 100644 --- a/packages/cli/src/lib/dsn/project-root.ts +++ b/packages/cli/src/lib/dsn/project-root.ts @@ -196,6 +196,7 @@ const statLimit = pLimit(STAT_CONCURRENCY); * Check if a path exists (file or directory) using stat. */ async function pathExists(filePath: string): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await stat(filePath); return true; @@ -238,6 +239,7 @@ async function anyGlobMatches( // surfaces when iterating. Wrap the full open+iterate in one try/catch. // No explicit handle.close() needed: for-await-of auto-closes the Dir // handle when the loop exits (including early return or break). + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { // Pre-compile matchers outside the loop to avoid recompiling per entry. const matchers = patterns.map((p) => picomatch(p, { dot: true })); @@ -391,6 +393,7 @@ function checkEnvForDsn(dir: string): Promise { * Returns home directory if it exists, otherwise filesystem root. */ export function getStopBoundary(): string { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return homedir(); } catch { diff --git a/packages/cli/src/lib/dsn/resolver.ts b/packages/cli/src/lib/dsn/resolver.ts index 0ac5f35e76..70649e97dc 100644 --- a/packages/cli/src/lib/dsn/resolver.ts +++ b/packages/cli/src/lib/dsn/resolver.ts @@ -158,10 +158,12 @@ type AccessibleProject = { export async function getAccessibleProjects(): Promise { const results: AccessibleProject[] = []; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const orgs = await listOrganizations(); for (const org of orgs) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const projects = await listProjects(org.slug); diff --git a/packages/cli/src/lib/error-reporting.ts b/packages/cli/src/lib/error-reporting.ts index a44c985968..5bd326650e 100644 --- a/packages/cli/src/lib/error-reporting.ts +++ b/packages/cli/src/lib/error-reporting.ts @@ -115,6 +115,7 @@ function recordSilencedError(error: unknown, reason: SilenceReason): void { attributes.auth_reason = error.reason; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { Sentry.metrics.distribution("cli.error.silenced", 1, { attributes }); } catch { @@ -124,6 +125,7 @@ function recordSilencedError(error: unknown, reason: SilenceReason): void { // Structured log for user API errors — `detail` is often the most actionable // field and is searchable in Sentry Logs. if (reason === "api_user_error" && error instanceof ApiError) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { Sentry.logger.info("cli.api_error_silenced", { status: error.status, diff --git a/packages/cli/src/lib/errors.ts b/packages/cli/src/lib/errors.ts index 666b297852..f81b8c70c6 100644 --- a/packages/cli/src/lib/errors.ts +++ b/packages/cli/src/lib/errors.ts @@ -764,6 +764,7 @@ export function stringifyUnknown(value: unknown): string { if (value && typeof value === "object") { // JSON.stringify can throw on circular references or BigInt values. // Fall back to String() which is always safe. + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.stringify(value); } catch { diff --git a/packages/cli/src/lib/formatters/conversation.ts b/packages/cli/src/lib/formatters/conversation.ts index c3ddbe8fa0..ceffd86446 100644 --- a/packages/cli/src/lib/formatters/conversation.ts +++ b/packages/cli/src/lib/formatters/conversation.ts @@ -189,6 +189,7 @@ function getOperationType(span: AgentConversationSpan): string | undefined { } function parseJson(value: string): unknown { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.parse(value); } catch { diff --git a/packages/cli/src/lib/formatters/markdown.ts b/packages/cli/src/lib/formatters/markdown.ts index 09d7ea93c3..e523b55a4e 100644 --- a/packages/cli/src/lib/formatters/markdown.ts +++ b/packages/cli/src/lib/formatters/markdown.ts @@ -237,6 +237,7 @@ function renderHtmlToken(raw: string): string { * language is unknown or highlighting fails. */ function highlightCode(code: string, language?: string): string { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return cliHighlight(code, { language, ignoreIllegals: true }); } catch { diff --git a/packages/cli/src/lib/formatters/sql.ts b/packages/cli/src/lib/formatters/sql.ts index 29e31cb624..46caf3c653 100644 --- a/packages/cli/src/lib/formatters/sql.ts +++ b/packages/cli/src/lib/formatters/sql.ts @@ -88,6 +88,7 @@ export function colorizeSql(sql: string): string { return sql; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const tokens = parser.parse(sql); return tokens.map(colorizeToken).join(""); @@ -124,6 +125,7 @@ export function formatSqlBlock(sql: string): string { return `\n─── Query ───\n\n${sql}\n`; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const tokens = parser.parse(sql); // Use sqlish's string formatter for structural formatting (newlines at keywords) diff --git a/packages/cli/src/lib/git.ts b/packages/cli/src/lib/git.ts index a029e01a67..546c83280d 100644 --- a/packages/cli/src/lib/git.ts +++ b/packages/cli/src/lib/git.ts @@ -91,6 +91,7 @@ function git(args: string[], cwd?: string): string { * @returns true if inside a git work tree */ export function isInsideGitWorkTree(cwd?: string): boolean { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { git(["rev-parse", "--is-inside-work-tree"], cwd); return true; @@ -109,6 +110,7 @@ export function isInsideGitWorkTree(cwd?: string): boolean { * @returns Array of formatted status lines (e.g., `["- M src/index.ts", "- ?? new-file.ts"]`) */ export function getUncommittedFiles(cwd?: string): string[] { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const raw = git(["status", "--porcelain=v1"], cwd); if (!raw) { @@ -152,6 +154,7 @@ export function getHeadCommit(cwd?: string): string { * @returns true if the repository is shallow */ export function isShallowRepository(cwd?: string): boolean { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return git(["rev-parse", "--is-shallow-repository"], cwd) === "true"; } catch { @@ -266,6 +269,7 @@ export function getCommitLog( * @returns `owner/repo` string, or undefined if no origin remote */ export function getRepositoryName(cwd?: string): string | undefined { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const url = git(["remote", "get-url", "origin"], cwd); return parseRemoteUrl(url); @@ -281,6 +285,7 @@ export function getRepositoryName(cwd?: string): string | undefined { * @returns The remote URL, or undefined when there is no origin remote */ export function getRemoteUrl(cwd?: string): string | undefined { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return git(["remote", "get-url", "origin"], cwd); } catch { @@ -295,6 +300,7 @@ export function getRemoteUrl(cwd?: string): string | undefined { * @returns The branch name, or undefined when detached (HEAD) or not a repo */ export function getCurrentBranch(cwd?: string): string | undefined { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const ref = git(["rev-parse", "--abbrev-ref", "HEAD"], cwd); return ref && ref !== "HEAD" ? ref : undefined; @@ -316,6 +322,7 @@ export function getMergeBase(ref: string, cwd?: string): string | undefined { if (ref.startsWith("-")) { return; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return git(["merge-base", "HEAD", ref], cwd) || undefined; } catch { @@ -333,6 +340,7 @@ export function getMergeBase(ref: string, cwd?: string): string | undefined { * (not set, shallow clone, or not a repo). */ export function getRemoteDefaultBranch(cwd?: string): string | undefined { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const ref = git(["symbolic-ref", "refs/remotes/origin/HEAD"], cwd); const prefix = "refs/remotes/origin/"; @@ -364,6 +372,7 @@ const DOT_GIT_SUFFIX_RE = /\.git$/; export function parseRemoteUrl(url: string): string | undefined { // Try URL parsing first — handles https://, ssh://, git:// protocols // (including ssh://git@host:port/path which would confuse the SCP regex) + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const parsed = new URL(url); const path = parsed.pathname @@ -399,6 +408,7 @@ export function inferRepositoryName( cwd?: string ): { name: string; remote: string } | undefined { for (const remote of ["upstream", "origin"]) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const url = git(["remote", "get-url", remote], cwd); const name = parseRemoteUrl(url); @@ -420,6 +430,7 @@ export function inferRepositoryName( * @returns The branch name, or "main" as fallback */ export function inferDefaultBranch(remote: string, cwd?: string): string { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const output = git(["symbolic-ref", `refs/remotes/${remote}/HEAD`], cwd); // refs/remotes/origin/main → main diff --git a/packages/cli/src/lib/hex-id-recovery.ts b/packages/cli/src/lib/hex-id-recovery.ts index 91304533fd..ea474cea1a 100644 --- a/packages/cli/src/lib/hex-id-recovery.ts +++ b/packages/cli/src/lib/hex-id-recovery.ts @@ -682,6 +682,7 @@ function recordRecoveryOutcome( input: string, result: RecoveryResult ): void { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { addBreadcrumb({ category: "hex_id_recovery", diff --git a/packages/cli/src/lib/init/preflight.ts b/packages/cli/src/lib/init/preflight.ts index 3b41673063..b4a29311be 100644 --- a/packages/cli/src/lib/init/preflight.ts +++ b/packages/cli/src/lib/init/preflight.ts @@ -212,6 +212,7 @@ async function resolveDetectedProject( } let detectedProject: { orgSlug: string; projectSlug: string } | null = null; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { detectedProject = await detectExistingProject(initial.directory); } catch { @@ -408,6 +409,7 @@ function canBypassMemberCreationRestriction(access: unknown): boolean { async function assertOrgScopedCreationCanProceed(org: string): Promise { let organization: Awaited>; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { organization = await getOrganization(org); } catch { @@ -591,6 +593,7 @@ async function detectExistingProject( return null; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { resolveDsnByPublicKey } = await import("../resolve-target.js"); const resolved = await resolveDsnByPublicKey(dsn); diff --git a/packages/cli/src/lib/init/stdin-reopen.ts b/packages/cli/src/lib/init/stdin-reopen.ts index 080515b6d3..466bdd112d 100644 --- a/packages/cli/src/lib/init/stdin-reopen.ts +++ b/packages/cli/src/lib/init/stdin-reopen.ts @@ -160,6 +160,7 @@ export function forwardFreshTtyToStdin( } let fd: number; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { fd = openTty(); } catch { @@ -283,6 +284,7 @@ export function closeFreshTtyForwarding(): void { // `setRawMode(false)`), the TTY is still in raw mode — leaving it there // produces a shell with no echo after a crash. Best-effort: the fresh fd // may already be destroyed from a prior error, so swallow any throw. + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { fresh.setRawMode(false); } catch { @@ -313,6 +315,7 @@ export function closeFreshTtyForwarding(): void { // user presses a key. Now that the original `.pause()` is restored, // invoke it directly so stock Node/Bun cleanup can finish. Idempotent: // safe when stdin was already paused. + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { original.pause.call(process.stdin); } catch { diff --git a/packages/cli/src/lib/init/tools/file-changes/prepare.ts b/packages/cli/src/lib/init/tools/file-changes/prepare.ts index bbe4b5c1f4..90230d25d2 100644 --- a/packages/cli/src/lib/init/tools/file-changes/prepare.ts +++ b/packages/cli/src/lib/init/tools/file-changes/prepare.ts @@ -178,6 +178,7 @@ function resolveCreateContent( } function prettyPrintJson(content: string): string { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return `${JSON.stringify(JSON.parse(content), null, 2)}\n`; } catch { @@ -246,6 +247,7 @@ async function prepareFileChange( let canonicalPath: string; let pathIdentity: string | undefined; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { canonicalPath = await resolveCanonicalDestination(absolutePath); if (!isCanonicalChild(rootRealPath, canonicalPath)) { @@ -327,6 +329,7 @@ async function prepareDelete( preparedPath: PreparedPath, change: Extract ): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const expected = await readDeleteSnapshot(preparedPath.absolutePath); if (!expected) { @@ -515,6 +518,7 @@ export async function prepareFileChanges( return { changes: prepared, ok: true }; } let rootRealPath: string; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { rootRealPath = await resolveCanonicalRoot(cwd); } catch { diff --git a/packages/cli/src/lib/init/tools/file-exists-batch.ts b/packages/cli/src/lib/init/tools/file-exists-batch.ts index 6553c3d800..d14ffae126 100644 --- a/packages/cli/src/lib/init/tools/file-exists-batch.ts +++ b/packages/cli/src/lib/init/tools/file-exists-batch.ts @@ -13,6 +13,7 @@ export async function fileExistsBatch( ): Promise { const results = await Promise.all( payload.params.paths.map(async (filePath) => { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const absPath = safePath(payload.cwd, filePath); await fs.promises.access(absPath); diff --git a/packages/cli/src/lib/init/tools/list-dir.ts b/packages/cli/src/lib/init/tools/list-dir.ts index dbdcf77ff5..f5bd2949e7 100644 --- a/packages/cli/src/lib/init/tools/list-dir.ts +++ b/packages/cli/src/lib/init/tools/list-dir.ts @@ -60,6 +60,7 @@ type WalkState = { }; async function readDirEntries(dir: string): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return await fs.promises.readdir(dir, { withFileTypes: true }); } catch { @@ -94,6 +95,7 @@ function toDirEntry( const relNative = abs.slice(state.cwdPrefixLen); if (entry.isSymbolicLink()) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { safePath(state.cwd, relNative); } catch { @@ -121,6 +123,7 @@ function toDirEntry( /** Return a regular file's byte size without opening or reading its contents. */ function fileSize(abs: string): { size?: number } { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const stat = fs.lstatSync(abs); return stat.isFile() ? { size: stat.size } : {}; diff --git a/packages/cli/src/lib/init/tools/project-file.ts b/packages/cli/src/lib/init/tools/project-file.ts index 1533295dc7..dc25a9c97a 100644 --- a/packages/cli/src/lib/init/tools/project-file.ts +++ b/packages/cli/src/lib/init/tools/project-file.ts @@ -89,6 +89,7 @@ export async function openProjectFile( } return { handle, stat }; } catch (error) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await handle?.close().catch(() => { // Preserve the primary open error when cleanup fails. }); @@ -99,6 +100,7 @@ export async function openProjectFile( export async function closeProjectFile( handle: fs.promises.FileHandle ): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await handle.close().catch(() => { // Descriptor cleanup must not replace the primary read classification. }); diff --git a/packages/cli/src/lib/init/tools/read-files.ts b/packages/cli/src/lib/init/tools/read-files.ts index ff7e5474f9..76ad0d338b 100644 --- a/packages/cli/src/lib/init/tools/read-files.ts +++ b/packages/cli/src/lib/init/tools/read-files.ts @@ -102,6 +102,7 @@ async function readSingleFileV2( } catch (error) { return { error: readErrorCode(error), status: "error" }; } finally { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await opened?.handle.close().catch(() => { // Preserve the primary read result when descriptor cleanup fails. }); @@ -324,6 +325,7 @@ function isTextChunk( ) { return false; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { decoder.decode(buffer, { stream: hasMoreBytes }); return true; diff --git a/packages/cli/src/lib/init/tools/shared.ts b/packages/cli/src/lib/init/tools/shared.ts index 3e732856f1..101ac2d995 100644 --- a/packages/cli/src/lib/init/tools/shared.ts +++ b/packages/cli/src/lib/init/tools/shared.ts @@ -17,6 +17,7 @@ export function safePath(cwd: string, relative: string): string { } let realCwd: string; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { realCwd = fs.realpathSync(normalizedCwd); } catch { @@ -58,6 +59,7 @@ export function validateToolSandbox( payload: Pick, directory: string ): { cwd: string } | ToolResult { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const realDirectory = fs.realpathSync(path.resolve(directory)); const realCwd = fs.realpathSync(path.resolve(payload.cwd)); diff --git a/packages/cli/src/lib/init/ui/factory.ts b/packages/cli/src/lib/init/ui/factory.ts index eb436cb6bd..fb3a4d1595 100644 --- a/packages/cli/src/lib/init/ui/factory.ts +++ b/packages/cli/src/lib/init/ui/factory.ts @@ -99,6 +99,7 @@ export async function getUIAsync(opts: UIFactoryOptions): Promise { if (shouldUseLogging(opts)) { return new LoggingUI(); } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { createInkUI } = await import("./ink-ui.js"); return await createInkUI({ initialWelcome: opts.initialWelcome }); diff --git a/packages/cli/src/lib/init/ui/ink-ui.ts b/packages/cli/src/lib/init/ui/ink-ui.ts index 17eb67e924..1a57b6dd6c 100644 --- a/packages/cli/src/lib/init/ui/ink-ui.ts +++ b/packages/cli/src/lib/init/ui/ink-ui.ts @@ -240,6 +240,7 @@ import inkAppPath from "./ink-app.tsx" with { type: "file" }; * broken in Bun-compiled binaries (see module docstring). */ function openFreshTtyForInk(): ReadStream | null { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const fd = openSync("/dev/tty", "r"); return new ReadStream(fd); @@ -276,6 +277,7 @@ export async function createInkUI( // Check if running inside a Node SEA binary let isSea = false; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { // biome-ignore lint/suspicious/noExplicitAny: node:sea types not yet in @types/node const sea = _require("node:sea") as any; @@ -310,6 +312,7 @@ export async function createInkUI( // Clean up SEA temp file — module is cached in memory after import() if (seaTmpDir) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { rmSync } = await import("node:fs"); rmSync(seaTmpDir, { recursive: true, force: true }); @@ -530,6 +533,7 @@ export class InkUI implements WizardUI { actions: { openUrl: (url) => { // Best-effort; openBrowser never throws, catch keeps it non-blocking. + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. openBrowser(url).catch(() => { // ignore }); @@ -927,11 +931,13 @@ export class InkUI implements WizardUI { // Detach the cancel callback from the store so a stale Ctrl+C // routed through the App after teardown can't re-enter. this.store.setRequestCancel(undefined); + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { this.instance.clear(); } catch { // best-effort } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { this.instance.unmount(); } catch { @@ -950,6 +956,7 @@ export class InkUI implements WizardUI { // left untouched so its compact summary flows into scrollback as before. const hasPostExitActions = this.store.getSnapshot().postExitActions.length > 0; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { process.stdout.write( hasPostExitActions ? "\x1b[?1049l\x1b[2J\x1b[H" : "\x1b[?1049l" @@ -958,11 +965,13 @@ export class InkUI implements WizardUI { // best-effort — stdout may already be destroyed } if (this.freshStdin) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { this.freshStdin.setRawMode(false); } catch { // stream already torn down } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { this.freshStdin.pause(); this.freshStdin.destroy(); diff --git a/packages/cli/src/lib/init/verify-setup.ts b/packages/cli/src/lib/init/verify-setup.ts index a6b4174e54..bf315a8f6a 100644 --- a/packages/cli/src/lib/init/verify-setup.ts +++ b/packages/cli/src/lib/init/verify-setup.ts @@ -386,6 +386,7 @@ export async function verifySetup( const envelopeReceived = new Promise((r) => { subscriptionId = buffer.subscribe((container) => { if (firstEventId === undefined) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const parsed = container.getParsedEnvelope(); const header = parsed?.envelope?.[0] as diff --git a/packages/cli/src/lib/init/wizard-runner.ts b/packages/cli/src/lib/init/wizard-runner.ts index 220dc1463b..e14ea46378 100644 --- a/packages/cli/src/lib/init/wizard-runner.ts +++ b/packages/cli/src/lib/init/wizard-runner.ts @@ -836,6 +836,7 @@ async function tryRecoverCurrentRunState( if (timeoutMs <= 0) { return null; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const raw = await withTimeout( workflow.runById(runId, { diff --git a/packages/cli/src/lib/init/workflow-inputs.ts b/packages/cli/src/lib/init/workflow-inputs.ts index 10e40cbf1a..45dc8f9ed7 100644 --- a/packages/cli/src/lib/init/workflow-inputs.ts +++ b/packages/cli/src/lib/init/workflow-inputs.ts @@ -148,6 +148,7 @@ async function readCommonConfigFile( filePath: string ): Promise { let opened: OpenedProjectFile | undefined; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const result = await openProjectFile(directory, filePath); if ("error" in result) { diff --git a/packages/cli/src/lib/logger.ts b/packages/cli/src/lib/logger.ts index c5acfb5f82..d6880d2b7e 100644 --- a/packages/cli/src/lib/logger.ts +++ b/packages/cli/src/lib/logger.ts @@ -267,6 +267,7 @@ export function attachSentryReporter(): void { return; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { // Dynamic import to avoid pulling in Sentry at module load time. // The reporter is exported from @sentry/node-core/light (via @sentry/node → @sentry/core). diff --git a/packages/cli/src/lib/oauth.ts b/packages/cli/src/lib/oauth.ts index 05e54d9207..a5ccd798fb 100644 --- a/packages/cli/src/lib/oauth.ts +++ b/packages/cli/src/lib/oauth.ts @@ -530,6 +530,7 @@ export function refreshAccessToken( if (!response.ok) { let errorDetail = "Token refresh failed"; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const errorData = await response.json(); const errorResult = safeParse(TokenErrorResponseSchema, errorData); diff --git a/packages/cli/src/lib/progress.ts b/packages/cli/src/lib/progress.ts index 97f88e5f49..b7e1a9db03 100644 --- a/packages/cli/src/lib/progress.ts +++ b/packages/cli/src/lib/progress.ts @@ -106,6 +106,7 @@ export function makeByteProgress( const emit = (): void => { // Cosmetic only — a formatting or callback failure must never propagate. + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { setMessage?.(render()); } catch { diff --git a/packages/cli/src/lib/react-native/wrap-call.ts b/packages/cli/src/lib/react-native/wrap-call.ts index 270fbf60a6..42d52117ac 100644 --- a/packages/cli/src/lib/react-native/wrap-call.ts +++ b/packages/cli/src/lib/react-native/wrap-call.ts @@ -30,6 +30,7 @@ export type SourceMapReport = { /** Whether this process is a Node Single Executable Application. */ export function isSea(): boolean { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const req = createRequire(import.meta.url); const sea = req("node:sea") as { isSea?: () => boolean }; @@ -53,6 +54,7 @@ function loadReport(path: string): SourceMapReport { if (!existsSync(path)) { return {}; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.parse(readFileSync(path, "utf-8")) as SourceMapReport; } catch { diff --git a/packages/cli/src/lib/region.ts b/packages/cli/src/lib/region.ts index 85827bafe1..aa98af4b46 100644 --- a/packages/cli/src/lib/region.ts +++ b/packages/cli/src/lib/region.ts @@ -178,6 +178,7 @@ export async function resolveEffectiveOrg(orgSlug: string): Promise { // Normal slug: try a single resolveOrgRegion() call (1 API request) // instead of the heavy listOrganizationsUncached() fan-out (1+N requests). // If it succeeds, the slug is valid and the region is now cached. + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await resolveOrgRegion(orgSlug); return orgSlug; @@ -190,6 +191,7 @@ export async function resolveEffectiveOrg(orgSlug: string): Promise { // DSN numeric ID: refresh the full org list to populate ID → slug mapping. // listOrganizationsUncached() populates org_regions with slug, region, org_id, and name. + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { listOrganizationsUncached } = await import("./api-client.js"); await listOrganizationsUncached(); diff --git a/packages/cli/src/lib/resolve-target.ts b/packages/cli/src/lib/resolve-target.ts index 03d7c538d0..caa1378692 100644 --- a/packages/cli/src/lib/resolve-target.ts +++ b/packages/cli/src/lib/resolve-target.ts @@ -319,6 +319,7 @@ async function normalizeNumericOrg(orgId: string): Promise { // Slow path: fetch org list to populate numeric ID → slug mapping. // resolveEffectiveOrg doesn't handle bare numeric IDs (only o-prefixed), // so we do a targeted refresh via listOrganizationsUncached(). + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { listOrganizationsUncached } = await import("./api-client.js"); await listOrganizationsUncached(); @@ -537,6 +538,7 @@ async function inferFromDirectoryName(cwd: string): Promise { // Search for matching projects using word-boundary matching let matches: Awaited>; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { matches = await findProjectsByPattern(dirName); } catch { @@ -652,6 +654,7 @@ async function findSimilarProjects( org: string, slug: string ): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const projects = await listProjects(org); const slugs = projects.map((p) => p.slug); @@ -683,6 +686,7 @@ async function findSimilarProjectsAcrossOrgs( * when the slug convention differs (e.g. underscores vs dashes). */ displayName?: string ): Promise<{ slug: string; orgSlug: string }[]> { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const concurrency = pLimit(5); const orgProjects = await Promise.all( @@ -1361,6 +1365,7 @@ export async function resolveOrgAndProject( } // 5. DSN auto-detection + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const dsnResult = await resolveFromDsn(cwd); if (dsnResult) { @@ -1701,6 +1706,7 @@ export async function resolveOrg( } // 5. DSN auto-detection + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const result = await resolveOrgFromDsn(cwd); if (result) { diff --git a/packages/cli/src/lib/resolve-team.ts b/packages/cli/src/lib/resolve-team.ts index 1a15b94da5..4a1288aac3 100644 --- a/packages/cli/src/lib/resolve-team.ts +++ b/packages/cli/src/lib/resolve-team.ts @@ -41,6 +41,7 @@ import { getSentryBaseUrl } from "./sentry-urls.js"; * @returns Formatted org list like "Your organizations:\n\n acme-corp\n other-org" */ async function fetchOrgListHint(fallbackHint: string): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const orgs = await listOrganizations(); if (orgs.length > 0) { diff --git a/packages/cli/src/lib/response-cache.ts b/packages/cli/src/lib/response-cache.ts index eaa0258407..47fe8dfc4e 100644 --- a/packages/cli/src/lib/response-cache.ts +++ b/packages/cli/src/lib/response-cache.ts @@ -441,6 +441,7 @@ export async function getCachedResponse( } let key: string; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { key = buildCacheKey(method, url); } catch { @@ -486,6 +487,7 @@ export async function getCachedResponse( // Best-effort cleanup of the broken entry. span.setAttribute("cache.hit", false); recordCacheHit("http", false); + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. unlink(cacheFilePath(key)).catch(() => { // Ignored — fire-and-forget }); @@ -506,6 +508,7 @@ export async function getCachedResponse( async function readCacheEntry(key: string): Promise { const filePath = cacheFilePath(key); let raw: string; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { raw = await readFile(filePath, "utf-8"); } catch { @@ -517,6 +520,7 @@ async function readCacheEntry(key: string): Promise { return JSON.parse(raw) as CacheEntry; } catch { // Corrupted cache file — delete it + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await unlink(filePath).catch(() => { // Best-effort cleanup of corrupted file }); @@ -555,6 +559,7 @@ export async function storeCachedResponse( } let key: string; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { key = buildCacheKey(method, url); } catch { @@ -562,6 +567,7 @@ export async function storeCachedResponse( return; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await withCacheSpan( url, @@ -676,6 +682,7 @@ async function writeResponseToCache(req: WriteRequest): Promise { // Probabilistic cleanup to avoid unbounded cache growth if (Math.random() < CLEANUP_PROBABILITY) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. cleanupCache().catch(() => { // Non-fatal: cleanup failure doesn't affect cache correctness }); @@ -698,6 +705,7 @@ async function writeResponseToCache(req: WriteRequest): Promise { export async function invalidateCachedResponsesMatching( prefix: string ): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const cacheDir = getCacheDir(); const files = await readdir(cacheDir); @@ -710,6 +718,7 @@ export async function invalidateCachedResponsesMatching( await cacheIO.map(jsonFiles, async (file) => { const filePath = join(cacheDir, file); + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const raw = await readFile(filePath, "utf-8"); const entry = JSON.parse(raw) as CacheEntry; @@ -717,6 +726,7 @@ export async function invalidateCachedResponsesMatching( entry.identity === currentIdentity && entry.url?.startsWith(prefix) ) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await unlink(filePath).catch(() => { /* another process may have deleted it */ }); @@ -735,6 +745,7 @@ export async function invalidateCachedResponsesMatching( * Called on `auth logout` and `auth login` since cached data is tied to the user. */ export async function clearResponseCache(): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await rm(getCacheDir(), { recursive: true, force: true }); } catch { @@ -816,6 +827,7 @@ async function deleteStaleTempFiles( try { const stats = await stat(filePath); if (stats.mtimeMs < cutoff) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await unlink(filePath).catch(() => { // Already gone — another sweep or the owning process removed it. }); @@ -889,6 +901,7 @@ async function deleteExpiredEntries( ): Promise { const expired = entries.filter((e) => e.expired); await cacheIO.map(expired, (entry) => + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. unlink(join(cacheDir, entry.file)).catch(() => { // Best-effort: file may have been deleted by another process }) @@ -908,6 +921,7 @@ async function evictExcessEntries( remaining.sort((a, b) => a.createdAt - b.createdAt); const toEvict = remaining.slice(0, remaining.length - MAX_CACHE_ENTRIES); await cacheIO.map(toEvict, (entry) => + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. unlink(join(cacheDir, entry.file)).catch(() => { // Best-effort eviction }) diff --git a/packages/cli/src/lib/scan/worker-pool.ts b/packages/cli/src/lib/scan/worker-pool.ts index 097d1d4016..47dfa28184 100644 --- a/packages/cli/src/lib/scan/worker-pool.ts +++ b/packages/cli/src/lib/scan/worker-pool.ts @@ -244,6 +244,7 @@ export function getWorkerPool(): WorkerPool { } pw.inflight = 0; unrefWorker(pw.worker); + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { pw.worker.terminate(); } catch { diff --git a/packages/cli/src/lib/sdk-invoke.ts b/packages/cli/src/lib/sdk-invoke.ts index 139b9507ce..7bcc3fdc7f 100644 --- a/packages/cli/src/lib/sdk-invoke.ts +++ b/packages/cli/src/lib/sdk-invoke.ts @@ -208,6 +208,7 @@ async function applyHeadersOption( /** Flush Sentry telemetry (no beforeExit handler in library mode). */ async function flushTelemetry(): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const Sentry = await import("@sentry/node-core/light"); const client = Sentry.getClient(); @@ -285,6 +286,7 @@ export function parseOutput( if (!stdoutStr.trim()) { return undefined as T; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return JSON.parse(stdoutStr) as T; } catch { diff --git a/packages/cli/src/lib/search-query.ts b/packages/cli/src/lib/search-query.ts index 9108fcbc7a..88196386de 100644 --- a/packages/cli/src/lib/search-query.ts +++ b/packages/cli/src/lib/search-query.ts @@ -348,6 +348,7 @@ export function sanitizeQuery(query: string | undefined): string | undefined { const normalized = normalizeQuery(query); let nodes: SearchNode[]; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { nodes = parse(normalized); } catch { diff --git a/packages/cli/src/lib/sentry-client.ts b/packages/cli/src/lib/sentry-client.ts index 53dd0cea1d..bb8b3653fa 100644 --- a/packages/cli/src/lib/sentry-client.ts +++ b/packages/cli/src/lib/sentry-client.ts @@ -372,6 +372,7 @@ function extractFullUrl(input: Request | string | URL): string { /** Extract the URL pathname for span naming */ function extractUrlPath(input: Request | string | URL): string { const raw = extractFullUrl(input); + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return new URL(raw).pathname; } catch { diff --git a/packages/cli/src/lib/sentry-url-parser.ts b/packages/cli/src/lib/sentry-url-parser.ts index 021e49c963..58fb720bdd 100644 --- a/packages/cli/src/lib/sentry-url-parser.ts +++ b/packages/cli/src/lib/sentry-url-parser.ts @@ -372,6 +372,7 @@ export function parseSentryUrl(input: string): ParsedSentryUrl | null { } let url: URL; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { url = new URL(input); } catch { diff --git a/packages/cli/src/lib/sentry-urls.ts b/packages/cli/src/lib/sentry-urls.ts index 44e3b78633..ea950ded5d 100644 --- a/packages/cli/src/lib/sentry-urls.ts +++ b/packages/cli/src/lib/sentry-urls.ts @@ -69,6 +69,7 @@ export function isSaaS(): boolean { * @returns true if the hostname is sentry.io or a subdomain of sentry.io */ export function isSentrySaasUrl(url: string): boolean { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const parsed = new URL(url); return ( @@ -99,6 +100,7 @@ export function isSentrySaasUrl(url: string): boolean { * @returns true only if the URL is a strictly-SaaS origin */ export function isSaaSTrustOrigin(url: string): boolean { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const parsed = new URL(url); return ( @@ -129,6 +131,7 @@ export function normalizeOrigin( } else { raw = input.url; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return new URL(raw).origin; } catch { @@ -241,6 +244,7 @@ export function parseOrgProjectFromSettingsUrl(url: string): { orgSlug?: string; projectSlug?: string; } { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const parsed = new URL(url); const segments = parsed.pathname.split("/").filter(Boolean); diff --git a/packages/cli/src/lib/shell.ts b/packages/cli/src/lib/shell.ts index 5980f42f66..a53603c77e 100644 --- a/packages/cli/src/lib/shell.ts +++ b/packages/cli/src/lib/shell.ts @@ -198,6 +198,7 @@ async function addToShellConfig( ); if (!exists) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await writeFile(configFile, `# sentry\n${command}\n`, "utf-8"); return { @@ -227,6 +228,7 @@ async function addToShellConfig( }; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const newContent = content.endsWith("\n") ? `${content}\n# sentry\n${command}\n` diff --git a/packages/cli/src/lib/sixel.ts b/packages/cli/src/lib/sixel.ts index 135806860b..4c634512e5 100644 --- a/packages/cli/src/lib/sixel.ts +++ b/packages/cli/src/lib/sixel.ts @@ -266,6 +266,7 @@ function probe(): SixelCaps { } let savedStty: string | undefined; let fd: number | undefined; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { fd = openSync("/dev/tty", "r+"); savedStty = execSync("stty -g < /dev/tty", { encoding: "utf8" }).trim(); @@ -280,6 +281,7 @@ function probe(): SixelCaps { return UNSUPPORTED; } finally { if (savedStty) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { execSync(`stty ${savedStty} < /dev/tty`); } catch { @@ -287,6 +289,7 @@ function probe(): SixelCaps { } } if (fd !== undefined) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { closeSync(fd); } catch { diff --git a/packages/cli/src/lib/sourcemap/zip.ts b/packages/cli/src/lib/sourcemap/zip.ts index 838019ac12..622ca2e044 100644 --- a/packages/cli/src/lib/sourcemap/zip.ts +++ b/packages/cli/src/lib/sourcemap/zip.ts @@ -149,6 +149,7 @@ export class ZipWriter { * Safe to call multiple times — subsequent calls are no-ops. */ async close(): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. await this.fh.close().catch(() => { // Already closed — ignore }); diff --git a/packages/cli/src/lib/telemetry.ts b/packages/cli/src/lib/telemetry.ts index 6708324142..8b99739adf 100644 --- a/packages/cli/src/lib/telemetry.ts +++ b/packages/cli/src/lib/telemetry.ts @@ -136,6 +136,7 @@ export function computeTelemetryEffective(): TelemetryEffective { return { enabled: false, source: `env:${DO_NOT_TRACK_ENV_VAR}` }; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const pref = getTelemetryPreference(); if (pref !== undefined) { @@ -189,6 +190,7 @@ export async function withTelemetry( // Flush deferred completion telemetry (queued during __complete fast-path). // Best-effort: never block CLI execution for telemetry emission. + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { drainCompletionTelemetry } = await import( "./db/completion-telemetry.js" @@ -287,6 +289,7 @@ export function createBeforeExitHandler( // Flush pending events before exit. Convert PromiseLike to Promise // for proper error handling. The async work causes beforeExit to // re-fire when complete, which the isFlushing guard handles. + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. Promise.resolve(client.flush(3000)).catch(() => { // Ignore flush errors — telemetry should never block CLI exit }); @@ -434,6 +437,7 @@ const LIBRARY_EXCLUDED_INTEGRATIONS = new Set([ * Checked once at module load so the integration filter is a simple boolean. */ const hasGetSystemErrorMap = (() => { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { // Dynamic require to avoid bundler issues — the check only matters at runtime const util = _require("node:util") as Record; @@ -1143,6 +1147,7 @@ const noop = (): void => {}; /** Resolves the database path, falling back to a default if the import fails. */ function resolveDbPath(): string { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const { getDbPath } = _require("./db/index.js") as { getDbPath: () => string; @@ -1215,6 +1220,7 @@ function isOwnedByRoot(filePath: string): boolean { if (process.platform === "win32") { return false; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return statSync(filePath).uid === 0; } catch { @@ -1249,6 +1255,7 @@ function tryRepairReadonly(): boolean { return false; } + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { // Repair config directory (needs rwx for WAL/SHM creation) chmodSync(configDir, 0o700); diff --git a/packages/cli/src/lib/telemetry/zstd-transport.ts b/packages/cli/src/lib/telemetry/zstd-transport.ts index 279a26bc4f..16b1cbdb50 100644 --- a/packages/cli/src/lib/telemetry/zstd-transport.ts +++ b/packages/cli/src/lib/telemetry/zstd-transport.ts @@ -101,6 +101,7 @@ export function makeCompressedTransport( options: NodeTransportOptions ): Transport { let urlSegments: URL; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { urlSegments = new URL(options.url); } catch { diff --git a/packages/cli/src/lib/timezone.ts b/packages/cli/src/lib/timezone.ts index 80bbbcc7ee..f459efcce3 100644 --- a/packages/cli/src/lib/timezone.ts +++ b/packages/cli/src/lib/timezone.ts @@ -54,6 +54,7 @@ const ZONEINFO_MARKER = "/zoneinfo/"; * disambiguates. */ export function runtimeTimezone(): string { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { return Intl.DateTimeFormat().resolvedOptions().timeZone || UTC_FALLBACK; } catch { @@ -106,6 +107,7 @@ export function detectOsTimezone(): string | null { function detectUnixTimezone(): string | null { // /etc/timezone is the canonical, greppable source on Debian/Ubuntu and // many container images. It holds a single IANA name. + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const contents = readFileSync("/etc/timezone", "utf-8").trim(); if (contents.length > 0 && contents !== UTC_FALLBACK) { @@ -117,6 +119,7 @@ function detectUnixTimezone(): string | null { // /etc/localtime is a symlink into the zoneinfo database on macOS and most // Linux distros. Its target encodes the IANA name. + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const target = readlinkSync("/etc/localtime"); return ianaFromZoneinfoPath(target); @@ -130,6 +133,7 @@ function detectUnixTimezone(): string | null { * Windows zone name to IANA where possible. */ function detectWindowsTimezone(): string | null { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const windowsName = execSync("tzutil /g", { encoding: "utf-8", diff --git a/packages/cli/src/lib/token-claims.ts b/packages/cli/src/lib/token-claims.ts index b478ed37b6..a5b75aef30 100644 --- a/packages/cli/src/lib/token-claims.ts +++ b/packages/cli/src/lib/token-claims.ts @@ -63,6 +63,7 @@ export function parseSntrysClaim( } let parsed: unknown; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { parsed = JSON.parse(Buffer.from(payloadEncoded, "base64").toString("utf8")); } catch { diff --git a/packages/cli/src/lib/trace-target.ts b/packages/cli/src/lib/trace-target.ts index c2abc77db6..6ffdfc43c5 100644 --- a/packages/cli/src/lib/trace-target.ts +++ b/packages/cli/src/lib/trace-target.ts @@ -361,6 +361,7 @@ async function recoveryContextFromTargetArg( return { org: "", project: undefined }; } let parsedTarget: ReturnType; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { parsedTarget = parseOrgProjectArg(targetArg); } catch { diff --git a/packages/cli/src/lib/upgrade.ts b/packages/cli/src/lib/upgrade.ts index 00a8301bf2..afe930f898 100644 --- a/packages/cli/src/lib/upgrade.ts +++ b/packages/cli/src/lib/upgrade.ts @@ -209,6 +209,7 @@ function runCommand( * @returns true if sentry is installed globally via this package manager */ async function isInstalledWith(pm: PackageManager): Promise { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const args = pm === "yarn" @@ -235,6 +236,7 @@ async function isInstalledWith(pm: PackageManager): Promise { */ function isHomebrewInstall(): boolean { let execPath = process.execPath; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { execPath = realpathSync(execPath); } catch { @@ -745,6 +747,7 @@ async function downloadStableToPath( const headers = getGitHubHeaders(); // Try gzip-compressed download first (~60% smaller) + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const gzResponse = await fetchWithUpgradeError( `${url}.gz`, @@ -888,6 +891,7 @@ export async function downloadBinaryToTemp( try { // Clean up any leftover temp file from interrupted download + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { unlinkSync(tempPath); } catch { @@ -931,6 +935,7 @@ export async function downloadBinaryToTemp( // Clear consumed patch cache — patches for the old version are useless // after the binary has been updated (whether via delta or full download). + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. clearPatchCache().catch(() => { /* best-effort — don't fail the upgrade if cache cleanup fails */ }); diff --git a/packages/cli/src/lib/utils.ts b/packages/cli/src/lib/utils.ts index 993ed893f1..ba807d4670 100644 --- a/packages/cli/src/lib/utils.ts +++ b/packages/cli/src/lib/utils.ts @@ -58,6 +58,7 @@ export function slugify(name: string): string { export function getRealUsername(): string { // userInfo() can throw on systems with missing or corrupted passwd entries. let osUsername = ""; + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { osUsername = userInfo().username; } catch { diff --git a/packages/cli/src/lib/which.ts b/packages/cli/src/lib/which.ts index b7868b2a26..0db662bd18 100644 --- a/packages/cli/src/lib/which.ts +++ b/packages/cli/src/lib/which.ts @@ -28,6 +28,7 @@ export function whichSync( command: string, opts?: { PATH?: string } ): string | null { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { const isWindows = process.platform === "win32"; // If a custom PATH is provided, override it in the subprocess env. diff --git a/packages/cli/src/lib/wrangler.ts b/packages/cli/src/lib/wrangler.ts index 0f6fb05106..b713bdd811 100644 --- a/packages/cli/src/lib/wrangler.ts +++ b/packages/cli/src/lib/wrangler.ts @@ -370,6 +370,7 @@ async function hasWranglerConfig( } for (const filename of WRANGLER_CONFIG_FILES) { + // biome-ignore lint/plugin: grandfathered silent catch — see #1531; drain by adding log.debug()/log.warn() or re-throwing. try { await access(join(cwd, filename)); return true; diff --git a/packages/cli/test/script/check-error-patterns.test.ts b/packages/cli/test/script/check-error-patterns.test.ts index a0e144aba0..4da6df9f7a 100644 --- a/packages/cli/test/script/check-error-patterns.test.ts +++ b/packages/cli/test/script/check-error-patterns.test.ts @@ -2,9 +2,12 @@ * Tests for the error-pattern checker (script/check-error-patterns.ts). * * The script both runs standalone (globbing src/, printing, process.exit) and - * exports its detection + baseline logic so it can be unit-tested against string - * fixtures. We exercise the pure functions directly and run the whole check as a - * subprocess against the real source tree to guard the committed baseline. + * exports its detection logic so it can be unit-tested against string fixtures. + * We exercise the pure functions directly and run the whole check as a + * subprocess against the real source tree. + * + * Silent-catch detection moved to the Biome plugin + * `lint-rules/no-silent-catch.grit`; see #1531. */ import { spawnSync } from "node:child_process"; @@ -12,11 +15,8 @@ import { dirname, join } from "node:path"; import { fileURLToPath } from "node:url"; import { describe, expect, test } from "vitest"; import { - compareToBaseline, - countByFile, findAdHocTryPatterns, findContextErrorNewlines, - findSilentCatches, } from "../../script/check-error-patterns.ts"; const pkgRoot = join(dirname(fileURLToPath(import.meta.url)), "..", ".."); @@ -28,44 +28,6 @@ function runCheck(args: string[] = []) { }); } -describe("findSilentCatches", () => { - test("flags an empty catch", () => { - const src = "try { f(); } catch {}"; - expect(findSilentCatches(src, "a.ts")).toHaveLength(1); - }); - - test("flags a comment-only catch", () => { - const src = "try { f(); } catch (e) {\n // ignore\n}"; - expect(findSilentCatches(src, "a.ts")).toHaveLength(1); - }); - - test("flags a return-only catch", () => { - const src = "try { f(); } catch {\n return null;\n}"; - expect(findSilentCatches(src, "a.ts")).toHaveLength(1); - }); - - test("flags a return-only .catch() handler", () => { - const src = "p.catch((e) => {\n return null;\n});"; - expect(findSilentCatches(src, "a.ts")).toHaveLength(1); - }); - - test("allows a catch that logs", () => { - const src = "try { f(); } catch (e) {\n log.debug('x', e);\n}"; - expect(findSilentCatches(src, "a.ts")).toHaveLength(0); - }); - - test("allows a catch that re-throws", () => { - const src = "try { f(); } catch (e) {\n throw e;\n}"; - expect(findSilentCatches(src, "a.ts")).toHaveLength(0); - }); - - test("allows a catch that forwards the error", () => { - const src = - "try { f(); } catch (error) {\n return handleFetchError(error);\n}"; - expect(findSilentCatches(src, "a.ts")).toHaveLength(0); - }); -}); - describe("findContextErrorNewlines", () => { test("flags a multi-line command argument", () => { const src = 'throw new ContextError("issue", "run this\\nthen that");'; @@ -90,50 +52,8 @@ describe("findAdHocTryPatterns", () => { }); }); -describe("countByFile", () => { - test("groups violations into per-file counts", () => { - const counts = countByFile([ - { file: "a.ts", line: 1, message: "" }, - { file: "a.ts", line: 9, message: "" }, - { file: "b.ts", line: 3, message: "" }, - ]); - expect(counts).toEqual({ "a.ts": 2, "b.ts": 1 }); - }); -}); - -describe("compareToBaseline", () => { - test("reports a new silent catch as a regression", () => { - const drift = compareToBaseline({ "a.ts": 2 }, { "a.ts": 1 }); - expect(drift.regressions).toEqual([ - { file: "a.ts", baseline: 1, actual: 2 }, - ]); - expect(drift.improvements).toEqual([]); - }); - - test("reports a file absent from the baseline as a regression", () => { - const drift = compareToBaseline({ "new.ts": 1 }, {}); - expect(drift.regressions).toEqual([ - { file: "new.ts", baseline: 0, actual: 1 }, - ]); - }); - - test("reports a removed silent catch as an improvement (stale baseline)", () => { - const drift = compareToBaseline({ "a.ts": 1 }, { "a.ts": 3 }); - expect(drift.improvements).toEqual([ - { file: "a.ts", baseline: 3, actual: 1 }, - ]); - expect(drift.regressions).toEqual([]); - }); - - test("reports no drift when counts match", () => { - const drift = compareToBaseline({ "a.ts": 2 }, { "a.ts": 2 }); - expect(drift.regressions).toEqual([]); - expect(drift.improvements).toEqual([]); - }); -}); - describe("check-error-patterns (subprocess)", () => { - test("passes against the current source tree and committed baseline", () => { + test("passes against the current source tree", () => { const result = runCheck(); expect(result.status, result.stdout + result.stderr).toBe(0); expect(result.stdout).toContain("No error class anti-patterns found");