From 41e1f95055fb3fadd362d14f2873f4ed8a490823 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 17 Aug 2026 12:07:59 +0000 Subject: [PATCH 1/3] fix(dsn-cache): treat empty DSN rows as cache misses in getCachedDsn MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit When setCachedDetection() is called with an empty allDsns array (no DSNs found during detection), it writes dsn="" and project_id="" into the dsn_cache table. getCachedDsn() previously returned these entries as valid cache hits, causing downstream code to receive a CachedDsnEntry with an empty DSN string. This wastes time on verification attempts and inflates cache hit telemetry with bogus entries. Guard getCachedDsn() to return undefined (cache miss) when the stored DSN is empty, so the caller falls through to a fresh detection scan. Co-authored-by: Miguel Betegón --- packages/cli/src/lib/db/dsn-cache.ts | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/packages/cli/src/lib/db/dsn-cache.ts b/packages/cli/src/lib/db/dsn-cache.ts index deb5cbdfc6..1c1bc18584 100644 --- a/packages/cli/src/lib/db/dsn-cache.ts +++ b/packages/cli/src/lib/db/dsn-cache.ts @@ -147,6 +147,14 @@ export function getCachedDsn(directory: string): CachedDsnEntry | undefined { return; } + // Rows written by setCachedDetection() with an empty allDsns array store + // dsn="" and project_id="". Treat these as cache misses for the single-DSN + // path — callers expect a usable DSN when the result is defined. + if (!row.dsn) { + recordCacheHit("dsn", false); + return; + } + recordCacheHit("dsn", true); touchCacheEntry("dsn_cache", "directory", directory); return rowToCachedDsnEntry(row); From d0262290ddecf21aa6ccc0ce7f57b551651aaf8d Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 17 Aug 2026 12:10:04 +0000 Subject: [PATCH 2/3] fix(region): add debug logging to silent catches in resolveEffectiveOrg MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The two catch blocks in resolveEffectiveOrg() silently swallowed errors from resolveOrgRegion() and listOrganizationsUncached(), making it impossible to diagnose why org resolution fell back to the raw slug. Network errors, auth issues, and API failures were completely invisible in debug output. Add log.debug() calls so errors are visible with --verbose, following the project's catch-block logging standard. Co-authored-by: Miguel Betegón --- packages/cli/src/lib/region.ts | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/packages/cli/src/lib/region.ts b/packages/cli/src/lib/region.ts index 4ac5fb536c..ea9873f3ed 100644 --- a/packages/cli/src/lib/region.ts +++ b/packages/cli/src/lib/region.ts @@ -10,9 +10,12 @@ import { getConfiguredSentryUrl } from "./constants.js"; import { getOrgByNumericId, getOrgRegion, setOrgRegion } from "./db/regions.js"; import { stripDsnOrgPrefix } from "./dsn/index.js"; import { withAuthGuard } from "./errors.js"; +import { logger } from "./logger.js"; import { getSdkConfig } from "./sentry-client.js"; import { getSentryBaseUrl, isSentrySaasUrl } from "./sentry-urls.js"; +const log = logger.withTag("region"); + /** * Promise cache for org region resolution, keyed by orgSlug. * @@ -181,9 +184,8 @@ export async function resolveEffectiveOrg(orgSlug: string): Promise { try { await resolveOrgRegion(orgSlug); return orgSlug; - } catch { - // Org not found or auth error — fall through to return the original - // slug. The downstream API call will produce a relevant error. + } catch (error) { + log.debug(`resolveOrgRegion failed for '${orgSlug}', using raw slug`, error); return orgSlug; } } @@ -193,7 +195,8 @@ export async function resolveEffectiveOrg(orgSlug: string): Promise { try { const { listOrganizationsUncached } = await import("./api-client.js"); await listOrganizationsUncached(); - } catch { + } catch (error) { + log.debug(`Failed to refresh org list for numeric ID '${orgSlug}'`, error); return orgSlug; } From 79e653244dc2d197d6194739131fda56793cc036 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Mon, 17 Aug 2026 12:11:42 +0000 Subject: [PATCH 3/3] fix(api/projects): add debug logging to silent catches in project operations MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Multiple catch blocks in api/projects.ts silently swallowed errors: - listProjects: cache population failures invisible - seedProjectCaches: project/DSN cache seeding failures invisible - findProjectByDsnKey: per-region lookup failures invisible - tryGetPrimaryDsn: DSN key fetch failures invisible All of these are best-effort operations that correctly fall back on error, but the silent catches made it impossible to diagnose why DSN detection, project resolution, or shell completions were not working. Add log.debug() calls so errors are visible with --verbose. Co-authored-by: Miguel Betegón --- packages/cli/src/lib/api/projects.ts | 21 +++++++++++++-------- 1 file changed, 13 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/lib/api/projects.ts b/packages/cli/src/lib/api/projects.ts index 65085d5e2a..68dd364494 100644 --- a/packages/cli/src/lib/api/projects.ts +++ b/packages/cli/src/lib/api/projects.ts @@ -28,10 +28,13 @@ import { } from "../db/project-cache.js"; import { getCachedOrganizations } from "../db/regions.js"; import { type AuthGuardSuccess, withAuthGuard } from "../errors.js"; +import { logger } from "../logger.js"; import { getApiBaseUrl } from "../sentry-client.js"; import { buildProjectUrl } from "../sentry-urls.js"; import { isAllDigits } from "../utils.js"; +const log = logger.withTag("api.projects"); + import { API_MAX_PER_PAGE, apiRequestToRegion, @@ -77,8 +80,8 @@ export async function listProjects(orgSlug: string): Promise { const orgs = getCachedOrganizations(); const orgName = orgs.find((o) => o.slug === orgSlug)?.name ?? orgSlug; cacheProjectsForOrg(orgSlug, orgName, allResults); - } catch { - // Cache population is best-effort — never fail the command + } catch (error) { + log.debug(`Failed to cache projects for org '${orgSlug}'`, error); } return allResults; @@ -178,8 +181,8 @@ function seedProjectCaches( cacheProjectsForOrg(orgSlug, orgName, [ { id: project.id, slug: project.slug, name: project.name }, ]); - } catch { - // Best-effort — don't let cache failures break project creation + } catch (error) { + log.debug(`Failed to seed project cache for '${project.slug}'`, error); } if (dsn) { try { @@ -193,8 +196,8 @@ function seedProjectCaches( projectId: project.id, }); } - } catch { - // Best-effort — don't let cache failures break project creation + } catch (error) { + log.debug(`Failed to seed DSN key cache for '${project.slug}'`, error); } } } @@ -509,7 +512,8 @@ export async function findProjectByDsnKey( { params: { query: `dsn:${publicKey}` } } ); return data; - } catch { + } catch (error) { + log.debug(`DSN key lookup failed in region '${region.url}'`, error); return []; } }) @@ -631,7 +635,8 @@ export async function tryGetPrimaryDsn( const keys = await getProjectKeys(orgSlug, projectSlug); const activeKey = keys.find((k) => k.isActive); return activeKey?.dsn.public ?? keys[0]?.dsn.public ?? null; - } catch { + } catch (error) { + log.debug(`Failed to fetch DSN for '${orgSlug}/${projectSlug}'`, error); return null; } }