From 5e685e5ad33fc031acad91425f728406a585a996 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Tue, 20 Oct 2026 15:59:48 +0000 Subject: [PATCH 1/3] fix(cli): Require return await for returned promises Enable type-aware Oxlint (oxlint-tsgolint) and turn on typescript/return-await ("always") for packages/cli/src so returning an un-awaited promise from an async function fails lint. Returning without await drops the caller's frame from async stack traces. Fix the 89 existing hits. In sentry-client executeAttempt, move handleResponse out of the try block so awaiting it does not route response-handling errors through handleFetchError. Type-aware correctness rules with an existing backlog are turned off at the root for now. Drop baseUrl from two tsconfigs (unsupported by tsgo; paths already resolve relative to the config) and ignore the SvelteKit init-eval template, whose tsconfig extends a generated file. Fixes #1552 Co-authored-by: byk Co-Authored-By: Claude --- .oxlintrc.json | 16 +++- .../src/routes/local-workspace-route.tsx | 4 +- apps/local/tsconfig.app.json | 1 - docs/contributing/coding-guidelines.md | 3 + package.json | 1 + packages/cli/.cursor/rules/oxc.mdc | 2 + packages/cli/src/cli.ts | 4 +- packages/cli/src/commands/cli/upgrade.ts | 13 +++- packages/cli/src/commands/dashboard/create.ts | 5 +- packages/cli/src/commands/dashboard/list.ts | 5 +- .../cli/src/commands/dashboard/resolve.ts | 5 +- .../cli/src/commands/dashboard/restore.ts | 13 ++-- .../cli/src/commands/dashboard/revisions.ts | 13 ++-- packages/cli/src/commands/dashboard/view.ts | 13 ++-- .../cli/src/commands/dashboard/widget/add.ts | 15 ++-- .../src/commands/dashboard/widget/delete.ts | 15 ++-- .../cli/src/commands/dashboard/widget/edit.ts | 15 ++-- packages/cli/src/commands/docs/query.ts | 2 +- packages/cli/src/commands/event/view.ts | 4 +- packages/cli/src/commands/explore.ts | 2 +- packages/cli/src/commands/feedback/utils.ts | 7 +- packages/cli/src/commands/issue/utils.ts | 6 +- .../cli/src/commands/local/control-plane.ts | 8 +- packages/cli/src/commands/local/daemon.ts | 10 +-- packages/cli/src/commands/local/server.ts | 2 +- packages/cli/src/commands/log/view.ts | 2 +- packages/cli/src/commands/project/list.ts | 2 +- .../cli/src/commands/release/set-commits.ts | 6 +- packages/cli/src/lib/api/conversations.ts | 2 +- packages/cli/src/lib/api/explore.ts | 2 +- packages/cli/src/lib/api/infrastructure.ts | 2 +- packages/cli/src/lib/api/issue-app-links.ts | 2 +- .../cli/src/lib/api/issue-integrations.ts | 2 +- packages/cli/src/lib/api/issues.ts | 11 ++- packages/cli/src/lib/api/logs.ts | 2 +- packages/cli/src/lib/api/organizations.ts | 2 +- packages/cli/src/lib/api/projects.ts | 4 +- packages/cli/src/lib/api/releases.ts | 2 +- packages/cli/src/lib/api/replays.ts | 4 +- packages/cli/src/lib/api/status-page.ts | 2 +- packages/cli/src/lib/api/traces.ts | 11 ++- packages/cli/src/lib/dsn/detector.ts | 4 +- packages/cli/src/lib/dsn/project-root.ts | 7 +- .../cli/src/lib/envelope/event-send-dsn.ts | 2 +- packages/cli/src/lib/ghcr.ts | 2 +- packages/cli/src/lib/hex-id-recovery.ts | 2 +- .../cli/src/lib/init/tools/apply-patchset.ts | 2 +- packages/cli/src/lib/mcp.ts | 2 +- packages/cli/src/lib/org-list.ts | 6 +- packages/cli/src/lib/polling.ts | 2 +- packages/cli/src/lib/resolve-issue.ts | 2 +- packages/cli/src/lib/resolve-target.ts | 16 ++-- packages/cli/src/lib/resolve-team.ts | 2 +- packages/cli/src/lib/scan/walker.ts | 2 +- packages/cli/src/lib/sdk-invoke.ts | 4 +- packages/cli/src/lib/sentry-client.ts | 7 +- packages/cli/src/lib/telemetry.ts | 2 +- .../cli/src/lib/telemetry/zstd-transport.ts | 2 +- packages/cli/src/lib/trace-target.ts | 4 +- packages/cli/src/lib/upgrade.ts | 6 +- packages/mcp-cloudflare/tsconfig.client.json | 1 - pnpm-lock.yaml | 74 ++++++++++++++++++- pnpm-workspace.yaml | 1 + scripts/oxc-config.test.mjs | 13 ++++ 64 files changed, 274 insertions(+), 133 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index b0e0c0885..67cd51be7 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -2,6 +2,9 @@ "$schema": "./node_modules/oxlint/configuration_schema.json", "plugins": ["eslint", "typescript", "unicorn", "oxc", "jsx-a11y"], "jsPlugins": ["./packages/cli/lint-rules/cli-oxlint-plugin.js"], + "options": { + "typeAware": true + }, "categories": { "correctness": "error" }, @@ -11,10 +14,20 @@ "**/tsconfig*.json", "packages/toolkit-core/src/generated/**", "packages/toolkit-core/test/fixtures/**", - "packages/mcp-server-mocks/src/fixtures/**" + "packages/mcp-server-mocks/src/fixtures/**", + "packages/cli/test/init-eval/templates/sveltekit-app/**" ], "rules": { "no-unused-vars": "off", + "typescript/await-thenable": "off", + "typescript/no-base-to-string": "off", + "typescript/no-duplicate-type-constituents": "off", + "typescript/no-floating-promises": "off", + "typescript/no-misused-spread": "off", + "typescript/no-redundant-type-constituents": "off", + "typescript/require-array-sort-compare": "off", + "typescript/restrict-template-expressions": "off", + "typescript/unbound-method": "off", "jsx-a11y/interactive-supports-focus": "off" }, "overrides": [ @@ -62,6 +75,7 @@ { "files": ["packages/cli/src/**/*.{ts,tsx}"], "rules": { + "typescript/return-await": ["error", "always"], "typescript/no-explicit-any": "error", "typescript/no-non-null-assertion": "error" } diff --git a/apps/local/src/routes/local-workspace-route.tsx b/apps/local/src/routes/local-workspace-route.tsx index c0395984f..5b44a85fd 100644 --- a/apps/local/src/routes/local-workspace-route.tsx +++ b/apps/local/src/routes/local-workspace-route.tsx @@ -14,7 +14,7 @@ export function LocalWorkspaceRoute({ children }: { children: ReactNode }) { const workspaceQuery = useWorkspaceQueryState(); const navigateToWorkspace = (view: WorkspaceView) => { - navigate({ pathname: workspacePath(view), search: location.search }); + void navigate({ pathname: workspacePath(view), search: location.search }); }; const navigateToCommandSelection = ( @@ -32,7 +32,7 @@ export function LocalWorkspaceRoute({ children }: { children: ReactNode }) { search.set("trace", selection.trace); } const query = search.toString(); - navigate({ + void navigate({ pathname: workspacePath(view), search: query ? `?${query}` : "", }); diff --git a/apps/local/tsconfig.app.json b/apps/local/tsconfig.app.json index a76bcf6ce..3a104a4a3 100644 --- a/apps/local/tsconfig.app.json +++ b/apps/local/tsconfig.app.json @@ -10,7 +10,6 @@ /* Bundler mode */ "moduleResolution": "bundler", - "baseUrl": ".", "paths": { "@/*": ["./src/*"] }, diff --git a/docs/contributing/coding-guidelines.md b/docs/contributing/coding-guidelines.md index 5b00ec692..dd5950f49 100644 --- a/docs/contributing/coding-guidelines.md +++ b/docs/contributing/coding-guidelines.md @@ -30,6 +30,9 @@ product-specific implementation patterns. - Root, MCP, and CLI files use Oxlint and Oxfmt. The CLI-specific rules live in `packages/cli/lint-rules/cli-oxlint-plugin.js`, with inline Oxlint suppressions for intentional exceptions. +- Oxlint runs type-aware (`options.typeAware` in `.oxlintrc.json`, backed by + `oxlint-tsgolint`). Type-aware rules from the correctness category that + still have a backlog are turned off at the root; enable them once fixed. - VS Code uses the Oxc extension for formatting and linting. Run `pnpm exec oxfmt --write ` to format a file. diff --git a/package.json b/package.json index b4d09a6d5..05cb298fc 100644 --- a/package.json +++ b/package.json @@ -57,6 +57,7 @@ "lint-staged": "catalog:", "oxfmt": "catalog:", "oxlint": "catalog:", + "oxlint-tsgolint": "catalog:", "simple-git-hooks": "catalog:", "tsdown": "catalog:", "tsx": "catalog:", diff --git a/packages/cli/.cursor/rules/oxc.mdc b/packages/cli/.cursor/rules/oxc.mdc index ee26feab6..a306c9ef2 100644 --- a/packages/cli/.cursor/rules/oxc.mdc +++ b/packages/cli/.cursor/rules/oxc.mdc @@ -14,6 +14,8 @@ pre-commit hook uses the same tools for staged files. - Use `const`, Node `node:*` APIs, and argument arrays for child processes. - Await promises and handle errors at the right boundary. Never use an async Promise executor or silently discard a failed operation. +- Write `return await` when an async function returns a promise, so the caller + stays in async stack traces. Type-aware `typescript/return-await` enforces it. - Keep command output in `CommandOutput` and diagnostics in the tagged logger. - Avoid top-level browser/Node APIs that break supported runtimes. - Keep tests isolated, with assertions in `it()`/`test()` and no disabled cases. diff --git a/packages/cli/src/cli.ts b/packages/cli/src/cli.ts index 1248818cc..941da517e 100644 --- a/packages/cli/src/cli.ts +++ b/packages/cli/src/cli.ts @@ -759,7 +759,7 @@ export async function startCli(): Promise { // Completions are a fast-path (~1ms) — skip .sentryclirc I/O. if (args[0] === "__complete") { - return runCompletion(args.slice(1)).catch(() => { + return await runCompletion(args.slice(1)).catch(() => { // Completions should never crash — silently return no results process.exitCode = 0; }); @@ -781,7 +781,7 @@ export async function startCli(): Promise { // Gracefully degrade: project context is optional. } - return runCli(args).catch((err) => { + return await runCli(args).catch((err) => { process.stderr.write(`Fatal: ${redactCredentialText(String(err))}\n`); process.exitCode = 1; }); diff --git a/packages/cli/src/commands/cli/upgrade.ts b/packages/cli/src/commands/cli/upgrade.ts index 812cd0aeb..1bea6ee0c 100644 --- a/packages/cli/src/commands/cli/upgrade.ts +++ b/packages/cli/src/commands/cli/upgrade.ts @@ -692,7 +692,14 @@ async function executeStandardUpgrade(opts: { const downloadResult = await withProgress( { message: `Downloading ${target}...`, json }, async (setMessage) => - executeUpgrade(method, target, downloadTag, offline, setMessage, source), + await executeUpgrade( + method, + target, + downloadTag, + offline, + setMessage, + source, + ), ); if (downloadResult?.patchBytes) { @@ -780,7 +787,7 @@ async function migrateToStandaloneForNightly(opts: { const downloadResult = await withProgress( { message: `Downloading ${target}...`, json }, async (setMessage) => - executeUpgrade( + await executeUpgrade( "curl", target, downloadTag, @@ -1004,7 +1011,7 @@ export const upgradeCommand = buildCommand({ const resolved = await withProgress( { message: "Checking for updates...", json: flags.json }, async () => - resolveTargetWithFallback({ + await resolveTargetWithFallback({ resolveOpts: { method, channel, versionArg, channelChanged, flags }, versionArg, offline: flags.offline, diff --git a/packages/cli/src/commands/dashboard/create.ts b/packages/cli/src/commands/dashboard/create.ts index 556ba46ec..09ae609d5 100644 --- a/packages/cli/src/commands/dashboard/create.ts +++ b/packages/cli/src/commands/dashboard/create.ts @@ -174,8 +174,9 @@ export const createCommand = buildCommand({ title, widgets: [], projects: projectIds.length > 0 ? projectIds : undefined, - }).catch(async (error: unknown) => - enrichDashboardError(error, { orgSlug, operation: "create" }), + }).catch( + async (error: unknown) => + await enrichDashboardError(error, { orgSlug, operation: "create" }), ); const url = buildDashboardUrl(orgSlug, dashboard.id); diff --git a/packages/cli/src/commands/dashboard/list.ts b/packages/cli/src/commands/dashboard/list.ts index a3722ce82..c2035a8dc 100644 --- a/packages/cli/src/commands/dashboard/list.ts +++ b/packages/cli/src/commands/dashboard/list.ts @@ -461,8 +461,9 @@ export const listCommand = buildListCommand("dashboard", { afterId, glob, }), - ).catch(async (error: unknown) => - enrichDashboardError(error, { orgSlug, operation: "list" }), + ).catch( + async (error: unknown) => + await enrichDashboardError(error, { orgSlug, operation: "list" }), ); // Advance the pagination cursor stack diff --git a/packages/cli/src/commands/dashboard/resolve.ts b/packages/cli/src/commands/dashboard/resolve.ts index fdf75760a..bba704b96 100644 --- a/packages/cli/src/commands/dashboard/resolve.ts +++ b/packages/cli/src/commands/dashboard/resolve.ts @@ -334,8 +334,9 @@ export async function resolveDashboardId( const { data, nextCursor } = await listDashboardsPaginated(orgSlug, { perPage: API_MAX_PER_PAGE, cursor, - }).catch(async (error: unknown) => - enrichDashboardError(error, { orgSlug, operation: "list" }), + }).catch( + async (error: unknown) => + await enrichDashboardError(error, { orgSlug, operation: "list" }), ); // Match by ID/slug first (e.g. "default-overview"), then fall back to title const match = diff --git a/packages/cli/src/commands/dashboard/restore.ts b/packages/cli/src/commands/dashboard/restore.ts index 1e4d0d8cd..ad6507783 100644 --- a/packages/cli/src/commands/dashboard/restore.ts +++ b/packages/cli/src/commands/dashboard/restore.ts @@ -109,12 +109,13 @@ export const restoreCommand = buildCommand({ const dashboard = await withProgress( { message: `Restoring revision ${flags.revision}...`, json: flags.json }, () => restoreDashboardRevision(orgSlug, dashboardId, flags.revision), - ).catch(async (error: unknown) => - enrichDashboardError(error, { - orgSlug, - dashboardId, - operation: "update", - }), + ).catch( + async (error: unknown) => + await enrichDashboardError(error, { + orgSlug, + dashboardId, + operation: "update", + }), ); const outputData: RestoreResult = { diff --git a/packages/cli/src/commands/dashboard/revisions.ts b/packages/cli/src/commands/dashboard/revisions.ts index 3bce65b31..b8a87dcc7 100644 --- a/packages/cli/src/commands/dashboard/revisions.ts +++ b/packages/cli/src/commands/dashboard/revisions.ts @@ -190,12 +190,13 @@ export const revisionsCommand = buildCommand({ cursor = nc; } }, - ).catch(async (error: unknown) => - enrichDashboardError(error, { - orgSlug, - dashboardId, - operation: "view", - }), + ).catch( + async (error: unknown) => + await enrichDashboardError(error, { + orgSlug, + dashboardId, + operation: "view", + }), ); const trimmed = results.slice(0, flags.limit); diff --git a/packages/cli/src/commands/dashboard/view.ts b/packages/cli/src/commands/dashboard/view.ts index edf3c1891..437ef0538 100644 --- a/packages/cli/src/commands/dashboard/view.ts +++ b/packages/cli/src/commands/dashboard/view.ts @@ -258,12 +258,13 @@ export const viewCommand = buildCommand({ const dashboard = await withProgress( { message: "Fetching dashboard...", json: flags.json }, () => getDashboard(orgSlug, dashboardId), - ).catch(async (error: unknown) => - enrichDashboardError(error, { - orgSlug, - dashboardId, - operation: "view", - }), + ).catch( + async (error: unknown) => + await enrichDashboardError(error, { + orgSlug, + dashboardId, + operation: "view", + }), ); const regionUrl = await resolveOrgRegion(orgSlug); diff --git a/packages/cli/src/commands/dashboard/widget/add.ts b/packages/cli/src/commands/dashboard/widget/add.ts index 300e6db8d..99fdc1313 100644 --- a/packages/cli/src/commands/dashboard/widget/add.ts +++ b/packages/cli/src/commands/dashboard/widget/add.ts @@ -257,7 +257,7 @@ export const addCommand = buildCommand({ // GET current dashboard → append widget with layout → PUT const current = await getDashboard(orgSlug, dashboardId).catch( async (error: unknown) => - enrichDashboardError(error, { + await enrichDashboardError(error, { orgSlug, dashboardId, operation: "view", @@ -302,12 +302,13 @@ export const addCommand = buildCommand({ orgSlug, dashboardId, updateBody, - ).catch(async (error: unknown) => - enrichDashboardError(error, { - orgSlug, - dashboardId, - operation: "update", - }), + ).catch( + async (error: unknown) => + await enrichDashboardError(error, { + orgSlug, + dashboardId, + operation: "update", + }), ); const url = buildDashboardUrl(orgSlug, dashboardId); diff --git a/packages/cli/src/commands/dashboard/widget/delete.ts b/packages/cli/src/commands/dashboard/widget/delete.ts index 6d337d15c..c1fdf9123 100644 --- a/packages/cli/src/commands/dashboard/widget/delete.ts +++ b/packages/cli/src/commands/dashboard/widget/delete.ts @@ -127,7 +127,7 @@ export const deleteCommand = buildDeleteCommand( // GET current dashboard → find widget const current = await getDashboard(orgSlug, dashboardId).catch( async (error: unknown) => - enrichDashboardError(error, { + await enrichDashboardError(error, { orgSlug, dashboardId, operation: "view", @@ -158,12 +158,13 @@ export const deleteCommand = buildDeleteCommand( orgSlug, dashboardId, updateBody, - ).catch(async (error: unknown) => - enrichDashboardError(error, { - orgSlug, - dashboardId, - operation: "update", - }), + ).catch( + async (error: unknown) => + await enrichDashboardError(error, { + orgSlug, + dashboardId, + operation: "update", + }), ); yield new CommandOutput({ diff --git a/packages/cli/src/commands/dashboard/widget/edit.ts b/packages/cli/src/commands/dashboard/widget/edit.ts index f9a5d5750..bdb9a7399 100644 --- a/packages/cli/src/commands/dashboard/widget/edit.ts +++ b/packages/cli/src/commands/dashboard/widget/edit.ts @@ -357,7 +357,7 @@ export const editCommand = buildCommand({ // GET current dashboard → find widget → merge changes → PUT const current = await getDashboard(orgSlug, dashboardId).catch( async (error: unknown) => - enrichDashboardError(error, { + await enrichDashboardError(error, { orgSlug, dashboardId, operation: "view", @@ -394,12 +394,13 @@ export const editCommand = buildCommand({ orgSlug, dashboardId, updateBody, - ).catch(async (error: unknown) => - enrichDashboardError(error, { - orgSlug, - dashboardId, - operation: "update", - }), + ).catch( + async (error: unknown) => + await enrichDashboardError(error, { + orgSlug, + dashboardId, + operation: "update", + }), ); const url = buildDashboardUrl(orgSlug, dashboardId); diff --git a/packages/cli/src/commands/docs/query.ts b/packages/cli/src/commands/docs/query.ts index 5da26c6ab..ae9af6993 100644 --- a/packages/cli/src/commands/docs/query.ts +++ b/packages/cli/src/commands/docs/query.ts @@ -62,7 +62,7 @@ export const queryCommand = buildCommand({ rotatingMessages: DOCS_QUERY_PROGRESS_MESSAGES, rotationIntervalMs: 4000, }, - async () => queryDocs(query, detectedContext), + async () => await queryDocs(query, detectedContext), ); yield new CommandOutput({ ...result, detectedContext }); }, diff --git a/packages/cli/src/commands/event/view.ts b/packages/cli/src/commands/event/view.ts index 9cbd084af..63c764d35 100644 --- a/packages/cli/src/commands/event/view.ts +++ b/packages/cli/src/commands/event/view.ts @@ -747,7 +747,7 @@ async function fetchLatestEventData( project?: string, ): Promise { const event = await getLatestEvent(org, issueId); - return buildSingleEventViewData(org, event, spans, project); + return await buildSingleEventViewData(org, event, spans, project); } /** @@ -902,7 +902,7 @@ async function resolveIssueShortIdEvent( spans: number, ): Promise { const issue = await getIssueByShortId(org, issueShortId); - return fetchLatestEventData(org, issue.id, spans, issue.project?.slug); + return await fetchLatestEventData(org, issue.id, spans, issue.project?.slug); } /** Result from an issue-based shortcut (URL or short ID) */ diff --git a/packages/cli/src/commands/explore.ts b/packages/cli/src/commands/explore.ts index 1f558b580..699ad570c 100644 --- a/packages/cli/src/commands/explore.ts +++ b/packages/cli/src/commands/explore.ts @@ -559,7 +559,7 @@ function resolveDatasetConfig(params: { sort, query, fetch: async ({ cursor, limit, timeRange }) => - queryEvents(org, { + await queryEvents(org, { fields, dataset: resolved.dataset, query, diff --git a/packages/cli/src/commands/feedback/utils.ts b/packages/cli/src/commands/feedback/utils.ts index 986dc4361..b392dd701 100644 --- a/packages/cli/src/commands/feedback/utils.ts +++ b/packages/cli/src/commands/feedback/utils.ts @@ -94,7 +94,12 @@ export async function resolveFeedback( ): Promise { const parsed = parseIssueArg(feedbackArg); if (parsed.type === "selector") { - return resolveFeedbackSelector(parsed.selector, parsed.org, cwd, command); + return await resolveFeedbackSelector( + parsed.selector, + parsed.org, + cwd, + command, + ); } let resolved: Awaited>; diff --git a/packages/cli/src/commands/issue/utils.ts b/packages/cli/src/commands/issue/utils.ts index a29bc876e..bba6e794e 100644 --- a/packages/cli/src/commands/issue/utils.ts +++ b/packages/cli/src/commands/issue/utils.ts @@ -479,7 +479,11 @@ async function resolveProjectSearch( // Fall back to findProjectsBySlug for precise error messages // and retry the issue lookup (handles transient failures). - return resolveProjectSearchFallback(projectSlug, suffix, commandContext); + return await resolveProjectSearchFallback( + projectSlug, + suffix, + commandContext, + ); } /** diff --git a/packages/cli/src/commands/local/control-plane.ts b/packages/cli/src/commands/local/control-plane.ts index da8b850c5..1525e8524 100644 --- a/packages/cli/src/commands/local/control-plane.ts +++ b/packages/cli/src/commands/local/control-plane.ts @@ -167,7 +167,7 @@ export function createLocalControlPlane({ if (!isLoopbackBrowserRequest(c.req.raw)) { return c.body(null, 403); } - return loopbackCors(c, next); + return await loopbackCors(c, next); }, ); const hostedCors = cors({ @@ -185,7 +185,7 @@ export function createLocalControlPlane({ return c.body(null, 403); } if (!isHostedUiOrigin(origin)) { - return loopbackCors(c, next); + return await loopbackCors(c, next); } if (c.req.method === "OPTIONS") { if (c.req.header("access-control-request-method") !== "GET") { @@ -206,7 +206,7 @@ export function createLocalControlPlane({ if (c.req.method !== "GET") { return c.body(null, 403); } - return hostedCors(c, next); + return await hostedCors(c, next); }); app.use( `${CONTROL_PLANE_PREFIX}/sessions/:id/ui/*`, @@ -424,7 +424,7 @@ export function createLocalControlPlane({ ) { return c.body(null, 401); } - if (c.req.param("action") === "retention") return updateRetention(c); + if (c.req.param("action") === "retention") return await updateRetention(c); if (c.req.param("action") === "reset") { sessionStore.getBuffer(c.req.param("id")).clear(); return c.body(null, 204); diff --git a/packages/cli/src/commands/local/daemon.ts b/packages/cli/src/commands/local/daemon.ts index d30237e54..b5221f6e9 100644 --- a/packages/cli/src/commands/local/daemon.ts +++ b/packages/cli/src/commands/local/daemon.ts @@ -269,7 +269,7 @@ export async function claimDaemonOwnership( ) { await rm(path, { force: true }); // Claim while the guard is still held, even on the final recovery attempt. - return tryClaim(); + return await tryClaim(); } } } catch (error) { @@ -281,7 +281,7 @@ export async function claimDaemonOwnership( ) ) throw error; - return tryClaim(); + return await tryClaim(); } return undefined; }); @@ -341,7 +341,7 @@ async function discoverLocalDaemon( current.port !== state.port || current.version !== state.version ) { - return discoverLocalDaemon(waitForResponse); + return await discoverLocalDaemon(waitForResponse); } if (responds) return state; if (isProcessRunning(state.pid)) { @@ -357,7 +357,7 @@ async function discoverLocalDaemon( export async function getRunningLocalDaemon(): Promise< LocalDaemonState | undefined > { - return discoverLocalDaemon(); + return await discoverLocalDaemon(); } /** Wait for the stopped daemon to relinquish state and ownership, even while its PID lives. */ @@ -435,7 +435,7 @@ export async function ensureLocalDaemon({ ); } await rm(startupLockPath(directory), { force: true }); - return ensureLocalDaemon({ host, port }); + return await ensureLocalDaemon({ host, port }); } let child: ChildProcess | undefined; let ready = false; diff --git a/packages/cli/src/commands/local/server.ts b/packages/cli/src/commands/local/server.ts index 28a1e1a63..5df5560cf 100644 --- a/packages/cli/src/commands/local/server.ts +++ b/packages/cli/src/commands/local/server.ts @@ -287,7 +287,7 @@ export function buildApp( } if (!hostedStreamRequest) { - return localhostCors(c, next); + return await localhostCors(c, next); } if (c.req.method === "OPTIONS") { diff --git a/packages/cli/src/commands/log/view.ts b/packages/cli/src/commands/log/view.ts index be88e5386..33b928981 100644 --- a/packages/cli/src/commands/log/view.ts +++ b/packages/cli/src/commands/log/view.ts @@ -357,7 +357,7 @@ async function resolveTarget( throw new ContextError("Specific project", USAGE_HINT, []); case "auto-detect": - return resolveOrgAndProject({ cwd, usageHint: USAGE_HINT }); + return await resolveOrgAndProject({ cwd, usageHint: USAGE_HINT }); default: { const _exhaustiveCheck: never = parsed; diff --git a/packages/cli/src/commands/project/list.ts b/packages/cli/src/commands/project/list.ts index a01e5cc6d..de3fae11e 100644 --- a/packages/cli/src/commands/project/list.ts +++ b/packages/cli/src/commands/project/list.ts @@ -281,7 +281,7 @@ async function fetchAutoDetectProjects( flags: ListFlags, ): Promise<{ projects: ProjectWithOrg[]; nextCursor?: string }> { if (orgs.length === 1 && !flags.platform) { - return fetchPaginatedSafe(orgs[0] as string, flags.limit); + return await fetchPaginatedSafe(orgs[0] as string, flags.limit); } if (orgs.length > 0) { const results = await Promise.all(orgs.map(fetchOrgProjectsSafe)); diff --git a/packages/cli/src/commands/release/set-commits.ts b/packages/cli/src/commands/release/set-commits.ts index c9c74f020..2f23aad8f 100644 --- a/packages/cli/src/commands/release/set-commits.ts +++ b/packages/cli/src/commands/release/set-commits.ts @@ -167,7 +167,7 @@ async function setCommitsDefault( ): Promise { // Fast path: cached "no repos" — skip the API call entirely if (hasNoRepoIntegration(org)) { - return setCommitsFromLocal(org, version, cwd, { depth }); + return await setCommitsFromLocal(org, version, cwd, { depth }); } try { @@ -191,14 +191,14 @@ async function setCommitsDefault( "Could not auto-discover commits (no repository integration). " + "Falling back to local git history.", ); - return setCommitsFromLocal(org, version, cwd, { depth }); + return await setCommitsFromLocal(org, version, cwd, { depth }); } if (error instanceof ValidationError && error.field === "repository") { log.warn( `Auto-discovery failed: ${error.message}. ` + "Falling back to local git history.", ); - return setCommitsFromLocal(org, version, cwd, { depth }); + return await setCommitsFromLocal(org, version, cwd, { depth }); } throw error; } diff --git a/packages/cli/src/lib/api/conversations.ts b/packages/cli/src/lib/api/conversations.ts index e9d6184f2..88e7139d6 100644 --- a/packages/cli/src/lib/api/conversations.ts +++ b/packages/cli/src/lib/api/conversations.ts @@ -117,7 +117,7 @@ export async function listConversations( } = {}, ): Promise> { const regionUrl = await resolveOrgRegion(orgSlug); - return paginate(options, (perPage, cursor) => + return await paginate(options, (perPage, cursor) => fetchConversationsPage(regionUrl, orgSlug, { ...options, cursor }, perPage), ); } diff --git a/packages/cli/src/lib/api/explore.ts b/packages/cli/src/lib/api/explore.ts index f7301cf90..54bbcf7ce 100644 --- a/packages/cli/src/lib/api/explore.ts +++ b/packages/cli/src/lib/api/explore.ts @@ -187,7 +187,7 @@ export async function queryEvents( // Fast path: single-page fetch when limit fits in one API page if (limit <= API_MAX_PER_PAGE) { - return fetchEventsPage(regionUrl, orgSlug, options, perPage); + return await fetchEventsPage(regionUrl, orgSlug, options, perPage); } // Multi-page: accumulate rows across pages up to the requested limit diff --git a/packages/cli/src/lib/api/infrastructure.ts b/packages/cli/src/lib/api/infrastructure.ts index 9dfbe5d5c..af00846bb 100644 --- a/packages/cli/src/lib/api/infrastructure.ts +++ b/packages/cli/src/lib/api/infrastructure.ts @@ -447,7 +447,7 @@ export async function autoPaginate( ): Promise> { // Fast path: single-page fetch when limit fits in one API page if (limit <= API_MAX_PER_PAGE) { - return fetchPage(initialCursor); + return await fetchPage(initialCursor); } // Multi-page: accumulate rows across pages up to the requested limit diff --git a/packages/cli/src/lib/api/issue-app-links.ts b/packages/cli/src/lib/api/issue-app-links.ts index c40785cec..4e209ee18 100644 --- a/packages/cli/src/lib/api/issue-app-links.ts +++ b/packages/cli/src/lib/api/issue-app-links.ts @@ -196,7 +196,7 @@ export async function listAppIssueLinks( ): Promise { requireIssueTarget(orgSlug, issueId); const config = getSdkClientConfig(await resolveOrgRegion(orgSlug)); - return fetchAllPages( + return await fetchAllPages( async (cursor) => { const result = await listOrganizationIssueExternalIssues({ ...config, diff --git a/packages/cli/src/lib/api/issue-integrations.ts b/packages/cli/src/lib/api/issue-integrations.ts index 8263a6f69..d6aa25ead 100644 --- a/packages/cli/src/lib/api/issue-integrations.ts +++ b/packages/cli/src/lib/api/issue-integrations.ts @@ -204,7 +204,7 @@ async function listIntegrations( issueId: string, ): Promise { const config = getSdkClientConfig(await resolveOrgRegion(orgSlug)); - return fetchAllPages( + return await fetchAllPages( async (cursor) => { const result = await listOrganizationIssueIntegrations({ ...config, diff --git a/packages/cli/src/lib/api/issues.ts b/packages/cli/src/lib/api/issues.ts index 3f7daa8e1..e47f25650 100644 --- a/packages/cli/src/lib/api/issues.ts +++ b/packages/cli/src/lib/api/issues.ts @@ -614,10 +614,13 @@ export async function updateIssueStatus( } // Legacy global endpoint — works without org but not region-aware. - return apiRequest(`/issues/${encodeURIComponent(issueId)}/`, { - method: "PUT", - body, - }); + return await apiRequest( + `/issues/${encodeURIComponent(issueId)}/`, + { + method: "PUT", + body, + }, + ); } /** Result of a successful issue-merge operation. */ diff --git a/packages/cli/src/lib/api/logs.ts b/packages/cli/src/lib/api/logs.ts index adb9819c8..2d192387e 100644 --- a/packages/cli/src/lib/api/logs.ts +++ b/packages/cli/src/lib/api/logs.ts @@ -420,7 +420,7 @@ export async function getLogs( // Single batch — no splitting needed if (logIds.length <= API_MAX_PER_PAGE) { - return getLogsBatch(orgSlug, projectSlug, logIds, batchOptions); + return await getLogsBatch(orgSlug, projectSlug, logIds, batchOptions); } // Split into batches of API_MAX_PER_PAGE and fetch in parallel diff --git a/packages/cli/src/lib/api/organizations.ts b/packages/cli/src/lib/api/organizations.ts index 5b92b8d90..48fcc72bc 100644 --- a/packages/cli/src/lib/api/organizations.ts +++ b/packages/cli/src/lib/api/organizations.ts @@ -145,7 +145,7 @@ export async function listOrganizations(): Promise { } // Cache miss — fetch from API (also populates cache for next time) - return listOrganizationsUncached(credential); + return await listOrganizationsUncached(credential); } type OrganizationPageContext = { diff --git a/packages/cli/src/lib/api/projects.ts b/packages/cli/src/lib/api/projects.ts index 2c7ca0c43..7c084d9d8 100644 --- a/packages/cli/src/lib/api/projects.ts +++ b/packages/cli/src/lib/api/projects.ts @@ -686,7 +686,7 @@ export async function listProjectDsns( ): Promise> { const config = await getOrgSdkConfig(orgSlug); - return paginate(options, async (perPage, cursor) => { + return await paginate(options, async (perPage, cursor) => { // The SDK omits the shared per_page parameter supported by this endpoint. const query = { cursor, per_page: perPage }; const result = await listProjectKeys({ @@ -722,7 +722,7 @@ export async function listOrganizationDsns( const projectLookups = new Map>(); const limitProjectLookups = pLimit(ORG_FANOUT_CONCURRENCY); - return paginate(options, async (perPage, cursor) => { + return await paginate(options, async (perPage, cursor) => { // These shared query parameters are supported by the backend but absent // from the SDK type. -1 includes accessible projects outside the user's teams. const query = { cursor, per_page: perPage, project: -1 }; diff --git a/packages/cli/src/lib/api/releases.ts b/packages/cli/src/lib/api/releases.ts index 7e10a5492..51881a24d 100644 --- a/packages/cli/src/lib/api/releases.ts +++ b/packages/cli/src/lib/api/releases.ts @@ -495,7 +495,7 @@ export async function setCommitsAuto( if (previousCommit) { ref.previousCommit = previousCommit; } - return setCommitsWithRefs(orgSlug, version, [ref]); + return await setCommitsWithRefs(orgSlug, version, [ref]); } if (!result.nextCursor) { diff --git a/packages/cli/src/lib/api/replays.ts b/packages/cli/src/lib/api/replays.ts index f43793695..cedb8fa14 100644 --- a/packages/cli/src/lib/api/replays.ts +++ b/packages/cli/src/lib/api/replays.ts @@ -236,7 +236,7 @@ export async function listReplays( options: ListReplaysOptions = {}, ): Promise> { const regionUrl = await resolveOrgRegion(orgSlug); - return paginate( + return await paginate( options, (perPage, cursor) => fetchReplayPage(regionUrl, orgSlug, { options, perPage, cursor }), @@ -299,7 +299,7 @@ export async function resolveReplay( if (!match) { throw error; } - return getReplay(orgSlug, match.id); + return await getReplay(orgSlug, match.id); } } diff --git a/packages/cli/src/lib/api/status-page.ts b/packages/cli/src/lib/api/status-page.ts index da30f3d6a..3b273f8cb 100644 --- a/packages/cli/src/lib/api/status-page.ts +++ b/packages/cli/src/lib/api/status-page.ts @@ -103,7 +103,7 @@ export async function fetchSentryStatus( const normalized = baseUrl.replace(TRAILING_SLASHES, ""); const summary = await tryFetchStatuspageSummary(normalized); - return summary ?? probeSelfHostedHealth(normalized); + return await (summary ?? probeSelfHostedHealth(normalized)); } /** diff --git a/packages/cli/src/lib/api/traces.ts b/packages/cli/src/lib/api/traces.ts index a3193befd..97da21964 100644 --- a/packages/cli/src/lib/api/traces.ts +++ b/packages/cli/src/lib/api/traces.ts @@ -168,7 +168,10 @@ export async function getDetailedTrace( : undefined; if (timestamp !== undefined) { - return requestTrace(regionUrl, orgSlug, traceId, { ...shared, timestamp }); + return await requestTrace(regionUrl, orgSlug, traceId, { + ...shared, + timestamp, + }); } const spans = await requestTrace(regionUrl, orgSlug, traceId, { @@ -182,7 +185,7 @@ export async function getDetailedTrace( log.debug( `No spans for trace ${traceId} in ${TRACE_LOOKUP_STATS_PERIOD}; retrying ${TRACE_LOOKUP_FALLBACK_STATS_PERIOD}`, ); - return requestTrace(regionUrl, orgSlug, traceId, { + return await requestTrace(regionUrl, orgSlug, traceId, { ...shared, statsPeriod: TRACE_LOOKUP_FALLBACK_STATS_PERIOD, }); @@ -512,7 +515,7 @@ export async function listTransactions( options: ListTransactionsOptions = {}, ): Promise> { const regionUrl = await resolveOrgRegion(orgSlug); - return paginate(options, (perPage, cursor) => + return await paginate(options, (perPage, cursor) => fetchTransactionsPage( regionUrl, orgSlug, @@ -648,7 +651,7 @@ export async function listSpans( options: ListSpansOptions = {}, ): Promise> { const regionUrl = await resolveOrgRegion(orgSlug); - return paginate(options, (perPage, cursor) => + return await paginate(options, (perPage, cursor) => fetchSpansPage( regionUrl, orgSlug, diff --git a/packages/cli/src/lib/dsn/detector.ts b/packages/cli/src/lib/dsn/detector.ts index 1ca948397..069f47032 100644 --- a/packages/cli/src/lib/dsn/detector.ts +++ b/packages/cli/src/lib/dsn/detector.ts @@ -352,7 +352,7 @@ async function verifyCachedDsn( ): Promise { // Env var source (lowest priority) - check for higher-priority sources if (cached.source === "env") { - return verifyEnvVarCache(cwd, cached); + return await verifyEnvVarCache(cwd, cached); } // Env file source - check for higher-priority code DSNs first @@ -364,7 +364,7 @@ async function verifyCachedDsn( } // Verify file-based sources (code, env_file, config) - return verifyFileDsnCache(cwd, cached); + return await verifyFileDsnCache(cwd, cached); } /** diff --git a/packages/cli/src/lib/dsn/project-root.ts b/packages/cli/src/lib/dsn/project-root.ts index 67616ff85..40838b496 100644 --- a/packages/cli/src/lib/dsn/project-root.ts +++ b/packages/cli/src/lib/dsn/project-root.ts @@ -340,7 +340,7 @@ export function hasLanguageMarker(dir: string): Promise { if (await anyExists(dir, LANGUAGE_MARKERS)) { return true; } - return anyGlobMatches(dir, LANGUAGE_MARKER_GLOBS); + return await anyGlobMatches(dir, LANGUAGE_MARKER_GLOBS); }); } @@ -351,8 +351,9 @@ export function hasLanguageMarker(dir: string): Promise { * @returns True if any build system marker found */ export function hasBuildSystemMarker(dir: string): Promise { - return withFsSpan("hasBuildSystemMarker", async () => - anyExists(dir, BUILD_SYSTEM_MARKERS), + return withFsSpan( + "hasBuildSystemMarker", + async () => await anyExists(dir, BUILD_SYSTEM_MARKERS), ); } diff --git a/packages/cli/src/lib/envelope/event-send-dsn.ts b/packages/cli/src/lib/envelope/event-send-dsn.ts index d5027fb87..92342f174 100644 --- a/packages/cli/src/lib/envelope/event-send-dsn.ts +++ b/packages/cli/src/lib/envelope/event-send-dsn.ts @@ -78,7 +78,7 @@ export async function resolveEventSendDsn( return target.dsn.trim(); } if (target?.kind === "project") { - return dsnFromProjectTarget(target.target, cwd); + return await dsnFromProjectTarget(target.target, cwd); } const scanned = await resolveIngestDsn({}, cwd); if (scanned) { diff --git a/packages/cli/src/lib/ghcr.ts b/packages/cli/src/lib/ghcr.ts index 69312b589..0b00adeb7 100644 --- a/packages/cli/src/lib/ghcr.ts +++ b/packages/cli/src/lib/ghcr.ts @@ -655,5 +655,5 @@ export async function downloadLayerBlob( source: UpgradeSource = PRIMARY_UPGRADE_SOURCE, ): Promise { const response = await downloadNightlyBlob(token, digest, signal, source); - return response.arrayBuffer(); + return await response.arrayBuffer(); } diff --git a/packages/cli/src/lib/hex-id-recovery.ts b/packages/cli/src/lib/hex-id-recovery.ts index 6a1881e16..88256b261 100644 --- a/packages/cli/src/lib/hex-id-recovery.ts +++ b/packages/cli/src/lib/hex-id-recovery.ts @@ -805,7 +805,7 @@ async function recoverHexIdInternal( }; } - return runFuzzyLookup(input, entityType, candidate, ctx); + return await runFuzzyLookup(input, entityType, candidate, ctx); } /** diff --git a/packages/cli/src/lib/init/tools/apply-patchset.ts b/packages/cli/src/lib/init/tools/apply-patchset.ts index e2f0b294c..bd9271120 100644 --- a/packages/cli/src/lib/init/tools/apply-patchset.ts +++ b/packages/cli/src/lib/init/tools/apply-patchset.ts @@ -42,7 +42,7 @@ export async function applyPatchset( if (!prepared.ok) { return failedPreparation(prepared.failure); } - return applyPreparedFileChanges(prepared.changes, context.dryRun); + return await applyPreparedFileChanges(prepared.changes, context.dryRun); } /** diff --git a/packages/cli/src/lib/mcp.ts b/packages/cli/src/lib/mcp.ts index 9243c6d48..e5b8711e8 100644 --- a/packages/cli/src/lib/mcp.ts +++ b/packages/cli/src/lib/mcp.ts @@ -119,7 +119,7 @@ async function resolveMcpAccessTokenWithLogin( throw error; } - return resolveCliMcpAccessToken(config); + return await resolveCliMcpAccessToken(config); } } diff --git a/packages/cli/src/lib/org-list.ts b/packages/cli/src/lib/org-list.ts index 1ae63cb09..405bb5189 100644 --- a/packages/cli/src/lib/org-list.ts +++ b/packages/cli/src/lib/org-list.ts @@ -681,7 +681,7 @@ export async function handleAutoDetect( ); return results.flat(); } - return fetchAllOrgs(config); + return await fetchAllOrgs(config); }, ); @@ -941,7 +941,7 @@ export async function handleProjectSearch( `'${projectSlug}' is an organization, not a project. ` + `Listing all ${config.entityPlural} in '${projectSlug}'.`, ); - return orgAllFallback(resolution.org); + return await orgAllFallback(resolution.org); } throw new ResolutionError( `'${projectSlug}'`, @@ -1257,5 +1257,5 @@ export async function dispatchOrgScopedList( }; // oxlint-disable-next-line typescript/no-explicit-any -- safe — dispatch guarantees type match - return (handler as ModeHandler)(ctx); + return await (handler as ModeHandler)(ctx); } diff --git a/packages/cli/src/lib/polling.ts b/packages/cli/src/lib/polling.ts index e1a3ec3be..d70118957 100644 --- a/packages/cli/src/lib/polling.ts +++ b/packages/cli/src/lib/polling.ts @@ -227,7 +227,7 @@ export async function withProgress( (options.interactiveOnly && !process.stdout.isTTY) ) { // JSON mode or non-TTY: skip the spinner entirely, pass a no-op setMessage - return fn(() => { + return await fn(() => { /* spinner suppressed */ }); } diff --git a/packages/cli/src/lib/resolve-issue.ts b/packages/cli/src/lib/resolve-issue.ts index 2fd4a32f4..68b04fb7d 100644 --- a/packages/cli/src/lib/resolve-issue.ts +++ b/packages/cli/src/lib/resolve-issue.ts @@ -185,5 +185,5 @@ export async function resolveIssueId( } // 3. Full short ID (e.g., "SPOTLIGHT-5D") or numeric ID (e.g., "123456789") - return resolveWithOrgContext(input, options, commandHint); + return await resolveWithOrgContext(input, options, commandHint); } diff --git a/packages/cli/src/lib/resolve-target.ts b/packages/cli/src/lib/resolve-target.ts index 2111d6434..3d37ad0d5 100644 --- a/packages/cli/src/lib/resolve-target.ts +++ b/packages/cli/src/lib/resolve-target.ts @@ -1524,7 +1524,7 @@ export async function resolveAllTargets( } span.setAttribute("resolve.method", "dsn"); - return resolveDetectedDsns(detection); + return await resolveDetectedDsns(detection); }, { "resolve.mode": "multi" }, ); @@ -1821,7 +1821,7 @@ async function promptSelect( return sole.value; } const { selectWithFilter } = await import("./init/ui/ink-ui.js"); - return selectWithFilter({ message, options }); + return await selectWithFilter({ message, options }); } /** @@ -2125,7 +2125,11 @@ export async function resolveProjectBoundSlug( } if (resolution.kind === "fuzzy-project") { - return resolveFuzzyProjectBoundSlug(resolution, projectSlug, usageHint); + return await resolveFuzzyProjectBoundSlug( + resolution, + projectSlug, + usageHint, + ); } if (resolution.kind === "not-found") { @@ -2709,14 +2713,16 @@ export async function resolveOrgOptionalTarget( ); return withTelemetryContext({ org: resolution.org }); } - return resolveProjectBoundTarget(parsed, cwd, commandName, { + return await resolveProjectBoundTarget(parsed, cwd, commandName, { projectSearchResolution: resolution, usageHint, }); } // explicit, scoped search, and display names - return resolveProjectBoundTarget(parsed, cwd, commandName, { usageHint }); + return await resolveProjectBoundTarget(parsed, cwd, commandName, { + usageHint, + }); } /** diff --git a/packages/cli/src/lib/resolve-team.ts b/packages/cli/src/lib/resolve-team.ts index 99b7c6ed0..fbac214c0 100644 --- a/packages/cli/src/lib/resolve-team.ts +++ b/packages/cli/src/lib/resolve-team.ts @@ -182,7 +182,7 @@ export async function resolveOrCreateTeam( // No teams — auto-create one if a slug was provided if (teams.length === 0) { - return resolveEmptyTeams(orgSlug, options); + return await resolveEmptyTeams(orgSlug, options); } // Single team — auto-select diff --git a/packages/cli/src/lib/scan/walker.ts b/packages/cli/src/lib/scan/walker.ts index 412ea0c45..cc943c364 100644 --- a/packages/cli/src/lib/scan/walker.ts +++ b/packages/cli/src/lib/scan/walker.ts @@ -620,7 +620,7 @@ async function processEntry( return null; } } - return tryYieldFile( + return await tryYieldFile( { absPath: abs, relPath: rel, fileDepth }, cfg, ctx.stats, diff --git a/packages/cli/src/lib/sdk-invoke.ts b/packages/cli/src/lib/sdk-invoke.ts index 1f5f131a1..25cbb66a9 100644 --- a/packages/cli/src/lib/sdk-invoke.ts +++ b/packages/cli/src/lib/sdk-invoke.ts @@ -409,7 +409,7 @@ async function executeWithCapture( const { withTelemetry } = await import("./telemetry.js"); try { - await withTelemetry(async (span) => executor(captureCtx, span), { + await withTelemetry(async (span) => await executor(captureCtx, span), { libraryMode: true, }); } catch (thrown) { @@ -495,7 +495,7 @@ function executeWithStream( const { withTelemetry } = await import("./telemetry.js"); // oxlint-disable-next-line typescript/no-non-null-assertion -- captureCtx is assigned on the line above - await withTelemetry(async (span) => executor(captureCtx!, span), { + await withTelemetry(async (span) => await executor(captureCtx!, span), { libraryMode: true, }); diff --git a/packages/cli/src/lib/sentry-client.ts b/packages/cli/src/lib/sentry-client.ts index 1b9037108..6d28f5ffc 100644 --- a/packages/cli/src/lib/sentry-client.ts +++ b/packages/cli/src/lib/sentry-client.ts @@ -1036,8 +1036,9 @@ async function executeAttempt({ : input instanceof Request ? input.signal : undefined; + let response: Response; try { - const response = validatedRedirects + response = validatedRedirects ? await fetchFollowingValidatedRedirects({ input, init, @@ -1054,10 +1055,12 @@ async function executeAttempt({ timeoutMs, responseBodyMode, }); - return handleResponse(response, headers, credential, isLastAttempt); } catch (error) { return handleFetchError(error, signal, isLastAttempt); } + // Outside the try: response handling errors must not be treated as fetch + // failures (and retried). + return await handleResponse(response, headers, credential, isLastAttempt); } /** Singleton authenticated fetch instance - reused across all requests */ diff --git a/packages/cli/src/lib/telemetry.ts b/packages/cli/src/lib/telemetry.ts index 4d3673629..fa1ec7fe9 100644 --- a/packages/cli/src/lib/telemetry.ts +++ b/packages/cli/src/lib/telemetry.ts @@ -183,7 +183,7 @@ export async function withTelemetry( const enabled = isTelemetryEnabled(); const client = initSentry(enabled, options); if (!client?.getOptions().enabled) { - return callback(undefined); + return await callback(undefined); } // Initialize user and instance context diff --git a/packages/cli/src/lib/telemetry/zstd-transport.ts b/packages/cli/src/lib/telemetry/zstd-transport.ts index a00923814..f78cb5ebd 100644 --- a/packages/cli/src/lib/telemetry/zstd-transport.ts +++ b/packages/cli/src/lib/telemetry/zstd-transport.ts @@ -208,7 +208,7 @@ async function performRequest( headers["content-encoding"] = encodingApplied; } - return new Promise((resolve, reject) => { + return await new Promise((resolve, reject) => { const req = httpModule.request( { method: "POST", diff --git a/packages/cli/src/lib/trace-target.ts b/packages/cli/src/lib/trace-target.ts index 849fedf7c..a8fceb53f 100644 --- a/packages/cli/src/lib/trace-target.ts +++ b/packages/cli/src/lib/trace-target.ts @@ -516,7 +516,7 @@ export async function resolveTraceOrgOptionalProject( ); return { traceId: parsed.traceId, ...resolved }; } - return resolveTraceOrgProject(parsed, cwd, usageHint); + return await resolveTraceOrgProject(parsed, cwd, usageHint); } /** @@ -544,7 +544,7 @@ export async function resolveTraceOrgProject( case "project-search": // resolveProjectBoundTarget (called inside) sets telemetry context. - return resolveProjectSearchTarget(parsed, cwd, usageHint); + return await resolveProjectSearchTarget(parsed, cwd, usageHint); case "org-scoped": throw new ContextError("Specific project", usageHint, [ diff --git a/packages/cli/src/lib/upgrade.ts b/packages/cli/src/lib/upgrade.ts index 9a2d56e7c..3a45b0807 100644 --- a/packages/cli/src/lib/upgrade.ts +++ b/packages/cli/src/lib/upgrade.ts @@ -833,7 +833,7 @@ async function standaloneVersionExists( } if (source) { if (isNightlyVersion(version)) { - return nightlyVersionExists(version, source); + return await nightlyVersionExists(version, source); } const response = await fetchWithUpgradeError( getGitHubReleaseByTagUrl(version, source), @@ -874,7 +874,7 @@ export async function versionExists( source?: UpgradeSource, ): Promise { if (isNightlyVersion(version) || method === "curl" || method === "brew") { - return standaloneVersionExists(version, source); + return await standaloneVersionExists(version, source); } validateStableVersion(version, "Requested package version"); @@ -1477,7 +1477,7 @@ export async function executeUpgrade( ): Promise { switch (method) { case "curl": - return downloadBinaryToTemp( + return await downloadBinaryToTemp( version, downloadTag, offline, diff --git a/packages/mcp-cloudflare/tsconfig.client.json b/packages/mcp-cloudflare/tsconfig.client.json index 46a5df1c1..0bfc9a086 100644 --- a/packages/mcp-cloudflare/tsconfig.client.json +++ b/packages/mcp-cloudflare/tsconfig.client.json @@ -2,7 +2,6 @@ "extends": "@sentry/mcp-server-tsconfig/tsconfig.vite.json", "compilerOptions": { "tsBuildInfoFile": "./node_modules/.tmp/tsconfig.client.tsbuildinfo", - "baseUrl": ".", "paths": { "@/*": [ "./src/*" diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 96299f055..5b3caab0c 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -123,6 +123,9 @@ catalogs: oxlint: specifier: 1.87.0 version: 1.87.0 + oxlint-tsgolint: + specifier: 7.0.2004 + version: 7.0.2004 react: specifier: ^19.2.8 version: 19.2.8 @@ -214,7 +217,10 @@ importers: version: 0.72.0 oxlint: specifier: 'catalog:' - version: 1.87.0 + version: 1.87.0(oxlint-tsgolint@7.0.2004) + oxlint-tsgolint: + specifier: 'catalog:' + version: 7.0.2004 simple-git-hooks: specifier: 'catalog:' version: 2.13.1 @@ -364,7 +370,7 @@ importers: version: 28.1.0 oxlint: specifier: ^1.79.0 - version: 1.87.0 + version: 1.87.0(oxlint-tsgolint@7.0.2004) typescript: specifier: 'catalog:' version: 5.8.3 @@ -3147,6 +3153,36 @@ packages: cpu: [x64] os: [win32] + '@oxlint-tsgolint/darwin-arm64@7.0.2004': + resolution: {integrity: sha512-uJk3ttju7Lz+Nv8WG4CsLyewoL1qf/Ny8S8OksJ7Bm/F14Tc9gMfW6arM1PCc57IzH/X/I79YsCWTgxX1FKibQ==} + cpu: [arm64] + os: [darwin] + + '@oxlint-tsgolint/darwin-x64@7.0.2004': + resolution: {integrity: sha512-0BFfHTzDxNDFPcu0UZAXT6CyCJHAXM0dZveyDb4KEHa2qF/DSnOKOrmvK/wFuFol9kvijbGM9JfuOsor+zWXRw==} + cpu: [x64] + os: [darwin] + + '@oxlint-tsgolint/linux-arm64@7.0.2004': + resolution: {integrity: sha512-TAehQc+qdRXyDN3nl/r/3/oPrBVrbtrDeGMkFyG6mf+XphpoJ2lAMVFLclyvrVZCw02RbKROjyw8h72XwQQHcw==} + cpu: [arm64] + os: [linux] + + '@oxlint-tsgolint/linux-x64@7.0.2004': + resolution: {integrity: sha512-LPunZvm6ZbWPfHMAyfZf9ci3HHvNEzLejO8zdqO3GoVGoSEHqqufHUD7+OgeatVSaJP7uXDJ2HaNOHIoH7go4Q==} + cpu: [x64] + os: [linux] + + '@oxlint-tsgolint/win32-arm64@7.0.2004': + resolution: {integrity: sha512-FVWplzDImWhiuawnIZVETaQecv4GWom6Jv4Fx2xj7hhXXDZ5okEgiw1gTCImHTIQsi2ycC5oSxVwrn69bMh7iw==} + cpu: [arm64] + os: [win32] + + '@oxlint-tsgolint/win32-x64@7.0.2004': + resolution: {integrity: sha512-7PsxuUx11Ry9DYTWcljTzRbmZY9B6O9fFFfZV+aodQEMy6pfRT3fhoQRv9PtGN/89z1pPz3nFKrsFm6WqNtX7w==} + cpu: [x64] + os: [win32] + '@oxlint/binding-android-arm-eabi@1.87.0': resolution: {integrity: sha512-22tVIHlB9898OYsBKmYB6He1MCSqdTmK1BVWuSNUTVrpS0KWNege+U9uEYqGJ+KatQag0r/oU44i3OOs46z7HQ==} engines: {node: ^20.19.0 || >=22.12.0} @@ -7390,6 +7426,10 @@ packages: vite-plus: optional: true + oxlint-tsgolint@7.0.2004: + resolution: {integrity: sha512-vQ9WDF3fiZJvRdSDosNOflerOCOpwol6vWzVDxW76RcU19cjTh7engvkHLvmv+tzedR8fAiJUVUGr6IVZdkzXQ==} + hasBin: true + oxlint@1.87.0: resolution: {integrity: sha512-drxVlaW7Oqq7wSy1CN25znRu00aRq8n57Q2wniJRxCmtYVz07ufCm7EVDPQFZURrTgQIjzKrVEHAD/HcvJXObg==} engines: {node: ^20.19.0 || >=22.12.0} @@ -11176,6 +11216,24 @@ snapshots: '@oxfmt/binding-win32-x64-msvc@0.72.0': optional: true + '@oxlint-tsgolint/darwin-arm64@7.0.2004': + optional: true + + '@oxlint-tsgolint/darwin-x64@7.0.2004': + optional: true + + '@oxlint-tsgolint/linux-arm64@7.0.2004': + optional: true + + '@oxlint-tsgolint/linux-x64@7.0.2004': + optional: true + + '@oxlint-tsgolint/win32-arm64@7.0.2004': + optional: true + + '@oxlint-tsgolint/win32-x64@7.0.2004': + optional: true + '@oxlint/binding-android-arm-eabi@1.87.0': optional: true @@ -15847,7 +15905,16 @@ snapshots: '@oxfmt/binding-win32-ia32-msvc': 0.72.0 '@oxfmt/binding-win32-x64-msvc': 0.72.0 - oxlint@1.87.0: + oxlint-tsgolint@7.0.2004: + optionalDependencies: + '@oxlint-tsgolint/darwin-arm64': 7.0.2004 + '@oxlint-tsgolint/darwin-x64': 7.0.2004 + '@oxlint-tsgolint/linux-arm64': 7.0.2004 + '@oxlint-tsgolint/linux-x64': 7.0.2004 + '@oxlint-tsgolint/win32-arm64': 7.0.2004 + '@oxlint-tsgolint/win32-x64': 7.0.2004 + + oxlint@1.87.0(oxlint-tsgolint@7.0.2004): optionalDependencies: '@oxlint/binding-android-arm-eabi': 1.87.0 '@oxlint/binding-android-arm64': 1.87.0 @@ -15868,6 +15935,7 @@ snapshots: '@oxlint/binding-win32-arm64-msvc': 1.87.0 '@oxlint/binding-win32-ia32-msvc': 1.87.0 '@oxlint/binding-win32-x64-msvc': 1.87.0 + oxlint-tsgolint: 7.0.2004 p-limit@3.1.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 59296ea1c..c54a18646 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -74,6 +74,7 @@ catalog: open: ^10.1.2 oxfmt: 0.72.0 oxlint: 1.87.0 + oxlint-tsgolint: 7.0.2004 react: ^19.2.8 react-dom: ^19.2.8 react-markdown: ^9.1.0 diff --git a/scripts/oxc-config.test.mjs b/scripts/oxc-config.test.mjs index 6d8ed0804..b1340a450 100644 --- a/scripts/oxc-config.test.mjs +++ b/scripts/oxc-config.test.mjs @@ -70,3 +70,16 @@ test("CI lint checks formatting for commits that skip the pre-commit hook", () = assert.match(scripts.lint, /pnpm run lint:format/); assert.match(workflow, /^\s*- run: pnpm run lint$/m); }); + +test("CLI sources require awaited returned promises via type-aware Oxlint", () => { + const config = readRootJson(".oxlintrc.json"); + const packageJson = readRootJson("package.json"); + const cli = config.overrides.find( + ({ files }) => + files.length === 1 && files[0] === "packages/cli/src/**/*.{ts,tsx}", + ); + + assert.equal(config.options.typeAware, true); + assert.ok(packageJson.dependencies["oxlint-tsgolint"]); + assert.deepEqual(cli.rules["typescript/return-await"], ["error", "always"]); +}); From 420f0d614eb718073cfb2622fa6356c13a55bc4f Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 13:50:58 +0000 Subject: [PATCH 2/3] fix: Require return await repo-wide Move typescript/return-await ("always") from the CLI override to the root config (and apps/local's own config) and fix the ~100 remaining hits across MCP packages, apps/local and scripts. In mcp-cloudflare's /oauth/register guard, build the rejection response outside the try so a finalizeResponse failure is not swallowed by the malformed-body catch. Replace the dashboard `.catch(async (error) => enrichDashboardError(...))` call sites with a withDashboardErrors(ctx, fn) helper, drop the config assertion test, and document the void/await choices inline. Refs #1552, #1573 Co-authored-by: byk Co-Authored-By: Claude --- .oxlintrc.json | 2 +- apps/local/.oxlintrc.json | 1 + apps/local/src/App.integration.test.tsx | 2 +- .../src/routes/local-workspace-route.tsx | 3 + docs/contributing/coding-guidelines.md | 6 +- packages/cli/src/commands/dashboard/create.ts | 17 +++--- packages/cli/src/commands/dashboard/list.ts | 33 +++++------ .../cli/src/commands/dashboard/resolve.ts | 31 ++++++++-- .../cli/src/commands/dashboard/restore.ts | 26 +++++---- .../cli/src/commands/dashboard/revisions.ts | 49 ++++++++-------- packages/cli/src/commands/dashboard/view.ts | 23 ++++---- .../cli/src/commands/dashboard/widget/add.ts | 34 +++++------ .../src/commands/dashboard/widget/delete.ts | 34 +++++------ .../cli/src/commands/dashboard/widget/edit.ts | 34 +++++------ packages/cli/src/lib/polling.ts | 4 +- .../cli/test/commands/cli/upgrade.test.ts | 2 +- .../test/commands/dashboard/resolve.test.ts | 19 ++++++ .../commands/local/daemon-ownership.test.ts | 2 +- .../cli/test/commands/local/server.test.ts | 5 +- .../cli/test/commands/project/list.test.ts | 2 +- packages/cli/test/e2e/local.test.ts | 2 +- .../cli/test/fixtures/local-agent-server.ts | 2 +- .../test/lib/api-client.multiregion.test.ts | 8 +-- packages/cli/test/lib/api-client.test.ts | 4 +- .../cli/test/lib/api/issue-app-links.test.ts | 4 +- .../test/lib/api/issue-integrations.test.ts | 2 +- packages/cli/test/lib/build/index.test.ts | 2 +- packages/cli/test/lib/db/concurrent.test.ts | 2 +- packages/cli/test/lib/delta-upgrade.test.ts | 2 +- packages/cli/test/lib/ghcr.test.ts | 2 +- .../test/lib/init/ui/ink-ui-telemetry.test.ts | 4 +- packages/cli/test/lib/polling.test.ts | 4 +- .../cli/test/lib/sentry-client.auth.test.ts | 2 +- packages/mcp-cloudflare/src/server/app.ts | 2 +- packages/mcp-cloudflare/src/server/index.ts | 58 +++++++++---------- .../src/server/lib/approval-dialog.test.ts | 2 +- .../src/server/lib/approval-dialog.ts | 2 +- .../src/server/lib/mcp-handler.ts | 6 +- .../src/server/oauth/callback.test.ts | 6 +- .../src/server/oauth/routes/authorize.ts | 2 +- .../mcp-cloudflare/src/server/oauth/state.ts | 2 +- packages/mcp-core/src/api-client/client.ts | 22 +++---- .../internal/agents/azure-openai-provider.ts | 4 +- .../internal/agents/tools/dataset-fields.ts | 11 +++- .../src/tools/catalog-runtime/availability.ts | 2 +- .../src/tools/catalog/get-issue-details.ts | 2 +- .../src/tools/catalog/get-replay-details.ts | 4 +- .../src/tools/catalog/get-sentry-resource.ts | 22 +++---- .../src/tools/catalog/get-snapshot-image.ts | 2 +- .../src/tools/catalog/get-snapshot.ts | 2 +- .../src/tools/catalog/get-span-details.ts | 2 +- .../catalog/search-agent-conversations.ts | 2 +- .../src/tools/catalog/search-errors.ts | 2 +- .../src/tools/catalog/search-events.ts | 2 +- .../mcp-core/src/tools/catalog/search-logs.ts | 2 +- .../src/tools/catalog/search-metrics.ts | 2 +- .../src/tools/catalog/search-profiles.ts | 2 +- .../src/tools/catalog/search-replays.ts | 2 +- .../src/tools/catalog/search-traces.ts | 2 +- .../src/tools/catalog/support/alerts.ts | 4 +- .../src/tools/special/execute-tool.ts | 4 +- .../src/tools/support/dashboards/index.ts | 2 +- .../src/tools/support/issue-linking/index.ts | 6 +- .../src/tools/support/search-events/utils.ts | 2 +- packages/mcp-server/src/cli/commands/auth.ts | 6 +- packages/mcp-test-client/src/auth/oauth.ts | 6 +- packages/toolkit-core/src/region-cache.ts | 2 +- scripts/cli-nightly-watchdog.mjs | 2 +- scripts/oxc-config.test.mjs | 13 ----- 69 files changed, 309 insertions(+), 276 deletions(-) diff --git a/.oxlintrc.json b/.oxlintrc.json index 67cd51be7..561c13381 100644 --- a/.oxlintrc.json +++ b/.oxlintrc.json @@ -19,6 +19,7 @@ ], "rules": { "no-unused-vars": "off", + "typescript/return-await": ["error", "always"], "typescript/await-thenable": "off", "typescript/no-base-to-string": "off", "typescript/no-duplicate-type-constituents": "off", @@ -75,7 +76,6 @@ { "files": ["packages/cli/src/**/*.{ts,tsx}"], "rules": { - "typescript/return-await": ["error", "always"], "typescript/no-explicit-any": "error", "typescript/no-non-null-assertion": "error" } diff --git a/apps/local/.oxlintrc.json b/apps/local/.oxlintrc.json index 6fa991dad..72cf41a39 100644 --- a/apps/local/.oxlintrc.json +++ b/apps/local/.oxlintrc.json @@ -3,6 +3,7 @@ "plugins": ["react", "typescript", "oxc"], "rules": { "react/rules-of-hooks": "error", + "typescript/return-await": ["error", "always"], "react/only-export-components": ["warn", { "allowConstantExport": true }] } } diff --git a/apps/local/src/App.integration.test.tsx b/apps/local/src/App.integration.test.tsx index 850c2bb8a..df770a5f9 100644 --- a/apps/local/src/App.integration.test.tsx +++ b/apps/local/src/App.integration.test.tsx @@ -71,7 +71,7 @@ function envelope(transaction: string, options: EnvelopeOptions = {}): string { } async function startReceiver() { - return tryListen(buildApp(createSpotlightBuffer(10)), 0, "127.0.0.1"); + return await tryListen(buildApp(createSpotlightBuffer(10)), 0, "127.0.0.1"); } async function stopReceiver( diff --git a/apps/local/src/routes/local-workspace-route.tsx b/apps/local/src/routes/local-workspace-route.tsx index 5b44a85fd..69264414f 100644 --- a/apps/local/src/routes/local-workspace-route.tsx +++ b/apps/local/src/routes/local-workspace-route.tsx @@ -14,6 +14,8 @@ export function LocalWorkspaceRoute({ children }: { children: ReactNode }) { const workspaceQuery = useWorkspaceQueryState(); const navigateToWorkspace = (view: WorkspaceView) => { + // navigate() can return a promise; `void` marks this as deliberate + // fire-and-forget for no-floating-promises. void navigate({ pathname: workspacePath(view), search: location.search }); }; @@ -32,6 +34,7 @@ export function LocalWorkspaceRoute({ children }: { children: ReactNode }) { search.set("trace", selection.trace); } const query = search.toString(); + // Fire-and-forget, as in navigateToWorkspace. void navigate({ pathname: workspacePath(view), search: query ? `?${query}` : "", diff --git a/docs/contributing/coding-guidelines.md b/docs/contributing/coding-guidelines.md index dd5950f49..292e43391 100644 --- a/docs/contributing/coding-guidelines.md +++ b/docs/contributing/coding-guidelines.md @@ -31,8 +31,10 @@ product-specific implementation patterns. `packages/cli/lint-rules/cli-oxlint-plugin.js`, with inline Oxlint suppressions for intentional exceptions. - Oxlint runs type-aware (`options.typeAware` in `.oxlintrc.json`, backed by - `oxlint-tsgolint`). Type-aware rules from the correctness category that - still have a backlog are turned off at the root; enable them once fixed. + `oxlint-tsgolint`). `typescript/return-await` (`always`) is on repo-wide: + write `return await` in async functions so callers stay in async stack + traces. Type-aware correctness rules that still have a backlog are turned + off at the root; #1573 tracks enabling them. - VS Code uses the Oxc extension for formatting and linting. Run `pnpm exec oxfmt --write ` to format a file. diff --git a/packages/cli/src/commands/dashboard/create.ts b/packages/cli/src/commands/dashboard/create.ts index 09ae609d5..74db51f13 100644 --- a/packages/cli/src/commands/dashboard/create.ts +++ b/packages/cli/src/commands/dashboard/create.ts @@ -24,7 +24,7 @@ import { import { buildDashboardUrl } from "../../lib/sentry-web-urls.js"; import { setOrgProjectContext } from "../../lib/telemetry.js"; import type { DashboardDetail } from "../../types/dashboard.js"; -import { enrichDashboardError } from "./resolve.js"; +import { withDashboardErrors } from "./resolve.js"; type CreateFlags = { readonly json: boolean; @@ -170,13 +170,14 @@ export const createCommand = buildCommand({ const parsed = parseOrgProjectArg(targetArg); const { orgSlug, projectIds } = await resolveDashboardTarget(parsed, cwd); - const dashboard = await createDashboard(orgSlug, { - title, - widgets: [], - projects: projectIds.length > 0 ? projectIds : undefined, - }).catch( - async (error: unknown) => - await enrichDashboardError(error, { orgSlug, operation: "create" }), + const dashboard = await withDashboardErrors( + { orgSlug, operation: "create" }, + () => + createDashboard(orgSlug, { + title, + widgets: [], + projects: projectIds.length > 0 ? projectIds : undefined, + }), ); const url = buildDashboardUrl(orgSlug, dashboard.id); diff --git a/packages/cli/src/commands/dashboard/list.ts b/packages/cli/src/commands/dashboard/list.ts index c2035a8dc..496c494d9 100644 --- a/packages/cli/src/commands/dashboard/list.ts +++ b/packages/cli/src/commands/dashboard/list.ts @@ -38,7 +38,7 @@ import { import type { DashboardListItem } from "../../types/dashboard.js"; import type { Writer } from "../../types/index.js"; import { - enrichDashboardError, + withDashboardErrors, parseDashboardListArgs, resolveOrgFromTarget, } from "./resolve.js"; @@ -448,22 +448,21 @@ export const listCommand = buildListCommand("dashboard", { dashboards: results, cursorToStore, allTitles, - } = await withProgress( - { - message: `Fetching dashboards${titleFilter ? ` matching '${titleFilter}'` : ""} (up to ${flags.limit})...`, - json: flags.json, - }, - () => - fetchDashboards(orgSlug, { - limit: flags.limit, - perPage, - serverCursor, - afterId, - glob, - }), - ).catch( - async (error: unknown) => - await enrichDashboardError(error, { orgSlug, operation: "list" }), + } = await withDashboardErrors({ orgSlug, operation: "list" }, () => + withProgress( + { + message: `Fetching dashboards${titleFilter ? ` matching '${titleFilter}'` : ""} (up to ${flags.limit})...`, + json: flags.json, + }, + () => + fetchDashboards(orgSlug, { + limit: flags.limit, + perPage, + serverCursor, + afterId, + glob, + }), + ), ); // Advance the pagination cursor stack diff --git a/packages/cli/src/commands/dashboard/resolve.ts b/packages/cli/src/commands/dashboard/resolve.ts index bba704b96..48f6120b0 100644 --- a/packages/cli/src/commands/dashboard/resolve.ts +++ b/packages/cli/src/commands/dashboard/resolve.ts @@ -331,12 +331,13 @@ export async function resolveDashboardId( let cursor: string | undefined; for (let page = 0; page < MAX_PAGINATION_PAGES; page++) { - const { data, nextCursor } = await listDashboardsPaginated(orgSlug, { - perPage: API_MAX_PER_PAGE, - cursor, - }).catch( - async (error: unknown) => - await enrichDashboardError(error, { orgSlug, operation: "list" }), + const { data, nextCursor } = await withDashboardErrors( + { orgSlug, operation: "list" }, + () => + listDashboardsPaginated(orgSlug, { + perPage: API_MAX_PER_PAGE, + cursor, + }), ); // Match by ID/slug first (e.g. "default-overview"), then fall back to title const match = @@ -789,6 +790,24 @@ export async function enrichDashboardError( throw error; } +/** + * Run a dashboard API operation, converting its failures with + * {@link enrichDashboardError}. + * + * @param ctx - Context about the operation for building error messages + * @param fn - The operation to run + */ +export async function withDashboardErrors( + ctx: DashboardErrorContext, + fn: () => Promise, +): Promise { + try { + return await fn(); + } catch (error) { + return await enrichDashboardError(error, ctx); + } +} + /** * User-facing dataset synonyms resolved to the canonical Sentry widget type. * diff --git a/packages/cli/src/commands/dashboard/restore.ts b/packages/cli/src/commands/dashboard/restore.ts index ad6507783..21b2f6f7b 100644 --- a/packages/cli/src/commands/dashboard/restore.ts +++ b/packages/cli/src/commands/dashboard/restore.ts @@ -16,7 +16,7 @@ import { withProgress } from "../../lib/polling.js"; import { buildDashboardUrl } from "../../lib/sentry-web-urls.js"; import type { DashboardDetail } from "../../types/dashboard.js"; import { - enrichDashboardError, + withDashboardErrors, parseDashboardPositionalArgs, resolveDashboardId, resolveOrgFromTarget, @@ -106,16 +106,20 @@ export const restoreCommand = buildCommand({ ); const dashboardId = await resolveDashboardId(orgSlug, dashboardRef); - const dashboard = await withProgress( - { message: `Restoring revision ${flags.revision}...`, json: flags.json }, - () => restoreDashboardRevision(orgSlug, dashboardId, flags.revision), - ).catch( - async (error: unknown) => - await enrichDashboardError(error, { - orgSlug, - dashboardId, - operation: "update", - }), + const dashboard = await withDashboardErrors( + { + orgSlug, + dashboardId, + operation: "update", + }, + () => + withProgress( + { + message: `Restoring revision ${flags.revision}...`, + json: flags.json, + }, + () => restoreDashboardRevision(orgSlug, dashboardId, flags.revision), + ), ); const outputData: RestoreResult = { diff --git a/packages/cli/src/commands/dashboard/revisions.ts b/packages/cli/src/commands/dashboard/revisions.ts index b8a87dcc7..42e8299c6 100644 --- a/packages/cli/src/commands/dashboard/revisions.ts +++ b/packages/cli/src/commands/dashboard/revisions.ts @@ -33,7 +33,7 @@ import { buildDashboardUrl } from "../../lib/sentry-web-urls.js"; import type { DashboardRevision } from "../../types/dashboard.js"; import type { Writer } from "../../types/index.js"; import { - enrichDashboardError, + withDashboardErrors, parseDashboardPositionalArgs, resolveDashboardId, resolveOrgFromTarget, @@ -173,30 +173,31 @@ export const revisionsCommand = buildCommand({ let cursor = rawCursor; let nextCursor: string | undefined; - await withProgress( - { message: "Fetching revisions...", json: flags.json }, - async () => { - for (let page = 0; page < MAX_PAGINATION_PAGES; page++) { - const { data, nextCursor: nc } = - await listDashboardRevisionsPaginated(orgSlug, dashboardId, { - perPage, - cursor, - }); - results.push(...data); - nextCursor = nc; - if (results.length >= flags.limit || !nc) { - break; - } - cursor = nc; - } + await withDashboardErrors( + { + orgSlug, + dashboardId, + operation: "view", }, - ).catch( - async (error: unknown) => - await enrichDashboardError(error, { - orgSlug, - dashboardId, - operation: "view", - }), + () => + withProgress( + { message: "Fetching revisions...", json: flags.json }, + async () => { + for (let page = 0; page < MAX_PAGINATION_PAGES; page++) { + const { data, nextCursor: nc } = + await listDashboardRevisionsPaginated(orgSlug, dashboardId, { + perPage, + cursor, + }); + results.push(...data); + nextCursor = nc; + if (results.length >= flags.limit || !nc) { + break; + } + cursor = nc; + } + }, + ), ); const trimmed = results.slice(0, flags.limit); diff --git a/packages/cli/src/commands/dashboard/view.ts b/packages/cli/src/commands/dashboard/view.ts index 437ef0538..b34dfe7a1 100644 --- a/packages/cli/src/commands/dashboard/view.ts +++ b/packages/cli/src/commands/dashboard/view.ts @@ -36,7 +36,7 @@ import type { WidgetDataResult, } from "../../types/dashboard.js"; import { - enrichDashboardError, + withDashboardErrors, parseDashboardPositionalArgs, resolveDashboardId, resolveOrgFromTarget, @@ -255,16 +255,17 @@ export const viewCommand = buildCommand({ } // Fetch the dashboard definition (widget structure) - const dashboard = await withProgress( - { message: "Fetching dashboard...", json: flags.json }, - () => getDashboard(orgSlug, dashboardId), - ).catch( - async (error: unknown) => - await enrichDashboardError(error, { - orgSlug, - dashboardId, - operation: "view", - }), + const dashboard = await withDashboardErrors( + { + orgSlug, + dashboardId, + operation: "view", + }, + () => + withProgress( + { message: "Fetching dashboard...", json: flags.json }, + () => getDashboard(orgSlug, dashboardId), + ), ); const regionUrl = await resolveOrgRegion(orgSlug); diff --git a/packages/cli/src/commands/dashboard/widget/add.ts b/packages/cli/src/commands/dashboard/widget/add.ts index 99fdc1313..6d75dddee 100644 --- a/packages/cli/src/commands/dashboard/widget/add.ts +++ b/packages/cli/src/commands/dashboard/widget/add.ts @@ -24,7 +24,7 @@ import { } from "../../../types/dashboard.js"; import { buildWidgetFromFlags, - enrichDashboardError, + withDashboardErrors, normalizeDataset, parseDashboardPositionalArgs, resolveDashboardId, @@ -255,13 +255,13 @@ export const addCommand = buildCommand({ validateWidgetLayout(flags); // GET current dashboard → append widget with layout → PUT - const current = await getDashboard(orgSlug, dashboardId).catch( - async (error: unknown) => - await enrichDashboardError(error, { - orgSlug, - dashboardId, - operation: "view", - }), + const current = await withDashboardErrors( + { + orgSlug, + dashboardId, + operation: "view", + }, + () => getDashboard(orgSlug, dashboardId), ); const updateBody = prepareDashboardForUpdate(current); @@ -298,17 +298,13 @@ export const addCommand = buildCommand({ updateBody.widgets.push(newWidget); - const updated = await updateDashboard( - orgSlug, - dashboardId, - updateBody, - ).catch( - async (error: unknown) => - await enrichDashboardError(error, { - orgSlug, - dashboardId, - operation: "update", - }), + const updated = await withDashboardErrors( + { + orgSlug, + dashboardId, + operation: "update", + }, + () => updateDashboard(orgSlug, dashboardId, updateBody), ); const url = buildDashboardUrl(orgSlug, dashboardId); diff --git a/packages/cli/src/commands/dashboard/widget/delete.ts b/packages/cli/src/commands/dashboard/widget/delete.ts index c1fdf9123..ff630df0e 100644 --- a/packages/cli/src/commands/dashboard/widget/delete.ts +++ b/packages/cli/src/commands/dashboard/widget/delete.ts @@ -24,7 +24,7 @@ import { prepareDashboardForUpdate, } from "../../../types/dashboard.js"; import { - enrichDashboardError, + withDashboardErrors, parseDashboardPositionalArgs, resolveDashboardId, resolveOrgFromTarget, @@ -125,13 +125,13 @@ export const deleteCommand = buildDeleteCommand( const dashboardId = await resolveDashboardId(orgSlug, dashboardRef); // GET current dashboard → find widget - const current = await getDashboard(orgSlug, dashboardId).catch( - async (error: unknown) => - await enrichDashboardError(error, { - orgSlug, - dashboardId, - operation: "view", - }), + const current = await withDashboardErrors( + { + orgSlug, + dashboardId, + operation: "view", + }, + () => getDashboard(orgSlug, dashboardId), ); const widgets = current.widgets ?? []; @@ -154,17 +154,13 @@ export const deleteCommand = buildDeleteCommand( const updateBody = prepareDashboardForUpdate(current); updateBody.widgets.splice(widgetIndex, 1); - const updated = await updateDashboard( - orgSlug, - dashboardId, - updateBody, - ).catch( - async (error: unknown) => - await enrichDashboardError(error, { - orgSlug, - dashboardId, - operation: "update", - }), + const updated = await withDashboardErrors( + { + orgSlug, + dashboardId, + operation: "update", + }, + () => updateDashboard(orgSlug, dashboardId, updateBody), ); yield new CommandOutput({ diff --git a/packages/cli/src/commands/dashboard/widget/edit.ts b/packages/cli/src/commands/dashboard/widget/edit.ts index bdb9a7399..bb26892ec 100644 --- a/packages/cli/src/commands/dashboard/widget/edit.ts +++ b/packages/cli/src/commands/dashboard/widget/edit.ts @@ -28,7 +28,7 @@ import { } from "../../../types/dashboard.js"; import { applyGroupLimitAutoDefault, - enrichDashboardError, + withDashboardErrors, normalizeDataset, parseDashboardPositionalArgs, resolveDashboardId, @@ -355,13 +355,13 @@ export const editCommand = buildCommand({ const dashboardId = await resolveDashboardId(orgSlug, dashboardRef); // GET current dashboard → find widget → merge changes → PUT - const current = await getDashboard(orgSlug, dashboardId).catch( - async (error: unknown) => - await enrichDashboardError(error, { - orgSlug, - dashboardId, - operation: "view", - }), + const current = await withDashboardErrors( + { + orgSlug, + dashboardId, + operation: "view", + }, + () => getDashboard(orgSlug, dashboardId), ); const widgets = current.widgets ?? []; const widgetIndex = resolveWidgetIndex( @@ -390,17 +390,13 @@ export const editCommand = buildCommand({ updateBody.widgets[widgetIndex] = replacement; - const updated = await updateDashboard( - orgSlug, - dashboardId, - updateBody, - ).catch( - async (error: unknown) => - await enrichDashboardError(error, { - orgSlug, - dashboardId, - operation: "update", - }), + const updated = await withDashboardErrors( + { + orgSlug, + dashboardId, + operation: "update", + }, + () => updateDashboard(orgSlug, dashboardId, updateBody), ); const url = buildDashboardUrl(orgSlug, dashboardId); diff --git a/packages/cli/src/lib/polling.ts b/packages/cli/src/lib/polling.ts index d70118957..fb411dc11 100644 --- a/packages/cli/src/lib/polling.ts +++ b/packages/cli/src/lib/polling.ts @@ -226,7 +226,9 @@ export async function withProgress( isPlainOutput() || (options.interactiveOnly && !process.stdout.isTTY) ) { - // JSON mode or non-TTY: skip the spinner entirely, pass a no-op setMessage + // JSON mode or non-TTY: skip the spinner entirely, pass a no-op setMessage. + // The `await` doesn't change the result; it keeps withProgress in async + // stack traces (typescript/return-await). return await fn(() => { /* spinner suppressed */ }); diff --git a/packages/cli/test/commands/cli/upgrade.test.ts b/packages/cli/test/commands/cli/upgrade.test.ts index 0ef043ea2..8cb3419a6 100644 --- a/packages/cli/test/commands/cli/upgrade.test.ts +++ b/packages/cli/test/commands/cli/upgrade.test.ts @@ -463,7 +463,7 @@ describe("sentry cli upgrade", () => { setVersionCheckInfo("88.88.88"); mockFetch(async (url) => { requests.push(String(url)); - return response(); + return await response(); }); const { context, errors, getOutput, restore } = createMockContext({ homeDir: testDir, diff --git a/packages/cli/test/commands/dashboard/resolve.test.ts b/packages/cli/test/commands/dashboard/resolve.test.ts index eeb87fdd9..a85a813ad 100644 --- a/packages/cli/test/commands/dashboard/resolve.test.ts +++ b/packages/cli/test/commands/dashboard/resolve.test.ts @@ -18,6 +18,7 @@ import { resolveOrgFromTarget, resolveWidgetIndex, validateWidgetEnums, + withDashboardErrors, } from "../../../src/commands/dashboard/resolve.js"; vi.mock("../../../src/lib/api-client.js", async (importOriginal) => { @@ -810,6 +811,24 @@ describe("enrichDashboardError", () => { }); }); +describe("withDashboardErrors", () => { + test("returns the operation's result", async () => { + await expect( + withDashboardErrors({ orgSlug: "my-org", operation: "list" }, () => + Promise.resolve("ok"), + ), + ).resolves.toBe("ok"); + }); + + test("enriches operation failures", async () => { + await expect( + withDashboardErrors({ orgSlug: "my-org", operation: "list" }, () => + Promise.reject(new ApiError("Not Found", 404)), + ), + ).rejects.toThrow(ResolutionError); + }); +}); + // --------------------------------------------------------------------------- // normalizeDataset + validateWidgetEnums // --------------------------------------------------------------------------- diff --git a/packages/cli/test/commands/local/daemon-ownership.test.ts b/packages/cli/test/commands/local/daemon-ownership.test.ts index 682ed2ded..203f90704 100644 --- a/packages/cli/test/commands/local/daemon-ownership.test.ts +++ b/packages/cli/test/commands/local/daemon-ownership.test.ts @@ -113,7 +113,7 @@ describe("Local daemon ownership", () => { ), ).toBe(String(process.pid)); contender = claimDaemonOwnership(directory); - return fs.stat(owner); + return await fs.stat(owner); }); const first = claimDaemonOwnership(directory); const firstResult = first.then( diff --git a/packages/cli/test/commands/local/server.test.ts b/packages/cli/test/commands/local/server.test.ts index 3519c03e4..857e42e7b 100644 --- a/packages/cli/test/commands/local/server.test.ts +++ b/packages/cli/test/commands/local/server.test.ts @@ -369,8 +369,9 @@ describe("buildApp", () => { ).status, ).toBe(403); } - const fetchMock = vi.fn(async (url: string, init?: RequestInit) => - app.request(new URL(url).pathname, init), + const fetchMock = vi.fn( + async (url: string, init?: RequestInit) => + await app.request(new URL(url).pathname, init), ); vi.stubGlobal("fetch", fetchMock); try { diff --git a/packages/cli/test/commands/project/list.test.ts b/packages/cli/test/commands/project/list.test.ts index 72a5570ac..9f49d358f 100644 --- a/packages/cli/test/commands/project/list.test.ts +++ b/packages/cli/test/commands/project/list.test.ts @@ -581,7 +581,7 @@ describe("project list: comma-separated project slugs", () => { status: 403, }); } - return projectFetch(input, init); + return await projectFetch(input, init); }) as typeof globalThis.fetch; const { context } = createCommandContext(); diff --git a/packages/cli/test/e2e/local.test.ts b/packages/cli/test/e2e/local.test.ts index 81f76c617..df1a86e9c 100644 --- a/packages/cli/test/e2e/local.test.ts +++ b/packages/cli/test/e2e/local.test.ts @@ -21,7 +21,7 @@ const fetch = (globalThis as { __originalFetch: typeof globalThis.fetch }) const getConfigDir = useTestConfigDir("local-e2e-"); async function cli(...args: string[]) { - return runCli(["local", ...args, "--json"], { + return await runCli(["local", ...args, "--json"], { env: { SENTRY_CONFIG_DIR: getConfigDir(), SENTRY_CLI_NO_TELEMETRY: "1", diff --git a/packages/cli/test/fixtures/local-agent-server.ts b/packages/cli/test/fixtures/local-agent-server.ts index 9825ae34b..0970c5dd2 100644 --- a/packages/cli/test/fixtures/local-agent-server.ts +++ b/packages/cli/test/fixtures/local-agent-server.ts @@ -43,7 +43,7 @@ export function createLocalAgentServer() { const { prompt } = (await c.req.json()) as { prompt?: unknown }; const safePrompt = typeof prompt === "string" ? prompt : ""; - return startSpan( + return await startSpan( { name: "agent.run", op: "gen_ai.invoke_agent", diff --git a/packages/cli/test/lib/api-client.multiregion.test.ts b/packages/cli/test/lib/api-client.multiregion.test.ts index f5c44c10d..51ba7a5c8 100644 --- a/packages/cli/test/lib/api-client.multiregion.test.ts +++ b/packages/cli/test/lib/api-client.multiregion.test.ts @@ -80,18 +80,18 @@ function createMultiRegionMockFetch(handlers: { url.hostname === "127.0.0.1" ) { if (handlers.controlSilo) { - return handlers.controlSilo(req); + return await handlers.controlSilo(req); } } else if (url.hostname === "us.sentry.io") { if (handlers.usRegion) { - return handlers.usRegion(req); + return await handlers.usRegion(req); } } else if (url.hostname === "de.sentry.io" && handlers.euRegion) { - return handlers.euRegion(req); + return await handlers.euRegion(req); } if (handlers.default) { - return handlers.default(req); + return await handlers.default(req); } return new Response(JSON.stringify({ detail: "Not found" }), { diff --git a/packages/cli/test/lib/api-client.test.ts b/packages/cli/test/lib/api-client.test.ts index 83912265b..f58e463a9 100644 --- a/packages/cli/test/lib/api-client.test.ts +++ b/packages/cli/test/lib/api-client.test.ts @@ -82,7 +82,7 @@ function createMockFetch( // OAuth token refresh endpoint if (req.url.includes("/oauth/token/")) { if (options.oauthHandler) { - return options.oauthHandler(req); + return await options.oauthHandler(req); } return new Response( JSON.stringify({ @@ -100,7 +100,7 @@ function createMockFetch( // API requests - delegate to handler apiRequestCount += 1; - return apiRequestHandler(req, apiRequestCount); + return await apiRequestHandler(req, apiRequestCount); }; } diff --git a/packages/cli/test/lib/api/issue-app-links.test.ts b/packages/cli/test/lib/api/issue-app-links.test.ts index 42c06792e..d231318b3 100644 --- a/packages/cli/test/lib/api/issue-app-links.test.ts +++ b/packages/cli/test/lib/api/issue-app-links.test.ts @@ -356,7 +356,7 @@ describe("app issue-link action", () => { const request = new Request(input, init); const parsed = new globalThis.URL(request.url); if (!parsed.pathname.endsWith("/sentry-app-installations/")) { - return defaultFetch(input, init); + return await defaultFetch(input, init); } calls.push(request); if (parsed.searchParams.get("cursor") === "install-page-2") { @@ -383,7 +383,7 @@ describe("app issue-link action", () => { globalThis.fetch = mockFetch(async (input, init) => { const request = new Request(input, init); if (!request.url.includes("/sentry-app-installations/")) { - return defaultFetch(input, init); + return await defaultFetch(input, init); } calls.push(request); return json({ detail: "Installation access denied" }, 403); diff --git a/packages/cli/test/lib/api/issue-integrations.test.ts b/packages/cli/test/lib/api/issue-integrations.test.ts index dd0b5df64..bbe79128a 100644 --- a/packages/cli/test/lib/api/issue-integrations.test.ts +++ b/packages/cli/test/lib/api/issue-integrations.test.ts @@ -308,7 +308,7 @@ describe("native link API", () => { globalThis.fetch = mockFetch(async (input, init) => { const request = new Request(input, init); requests.push(request); - return respond(request); + return await respond(request); }); return requests; } diff --git a/packages/cli/test/lib/build/index.test.ts b/packages/cli/test/lib/build/index.test.ts index 9912a7f91..b6ad6d1ee 100644 --- a/packages/cli/test/lib/build/index.test.ts +++ b/packages/cli/test/lib/build/index.test.ts @@ -357,7 +357,7 @@ describe("normalizeIpa", () => { /** Write IPA bytes to a temp file and normalize it, returning the wrapper. */ async function normalizeIpaBytes(ipa: Uint8Array): Promise { const src = writeTmpFile("app.ipa", ipa); - return normalizeToBuffer((out) => normalizeIpa(src, out, null)); + return await normalizeToBuffer((out) => normalizeIpa(src, out, null)); } test("remaps Payload into an XCArchive layout with a generated Info.plist", async () => { diff --git a/packages/cli/test/lib/db/concurrent.test.ts b/packages/cli/test/lib/db/concurrent.test.ts index f71d22d80..1ed19ed18 100644 --- a/packages/cli/test/lib/db/concurrent.test.ts +++ b/packages/cli/test/lib/db/concurrent.test.ts @@ -110,7 +110,7 @@ async function spawnWorkersConcurrently( promises.push(spawnWorker(configDir, String(i), operation)); } - return Promise.all(promises); + return await Promise.all(promises); } describe("concurrent database access", () => { diff --git a/packages/cli/test/lib/delta-upgrade.test.ts b/packages/cli/test/lib/delta-upgrade.test.ts index 3f2300ebc..fdb8314fe 100644 --- a/packages/cli/test/lib/delta-upgrade.test.ts +++ b/packages/cli/test/lib/delta-upgrade.test.ts @@ -1935,7 +1935,7 @@ async function importDeltaUpgradeWithVersion(version: string) { await importOriginal(); return { ...actual, CLI_VERSION: version }; }); - return import("../../src/lib/delta-upgrade.js"); + return await import("../../src/lib/delta-upgrade.js"); } function restoreDeltaUpgradeModule(): void { diff --git a/packages/cli/test/lib/ghcr.test.ts b/packages/cli/test/lib/ghcr.test.ts index 533e4c6ee..6d8867a14 100644 --- a/packages/cli/test/lib/ghcr.test.ts +++ b/packages/cli/test/lib/ghcr.test.ts @@ -131,7 +131,7 @@ describe("getAnonymousToken", () => { let requests = 0; mockFetch(async (_url, init) => { requests += 1; - return new Promise((_resolve, reject) => { + return await new Promise((_resolve, reject) => { init?.signal?.addEventListener( "abort", () => reject(new DOMException("aborted", "AbortError")), diff --git a/packages/cli/test/lib/init/ui/ink-ui-telemetry.test.ts b/packages/cli/test/lib/init/ui/ink-ui-telemetry.test.ts index 954ce7b20..b0df7ede3 100644 --- a/packages/cli/test/lib/init/ui/ink-ui-telemetry.test.ts +++ b/packages/cli/test/lib/init/ui/ink-ui-telemetry.test.ts @@ -83,7 +83,7 @@ describe("InkUI prompt telemetry", () => { message: "Choose features", options: [{ label: "Tracing", value: "performanceMonitoring" }], }); - return ui.select({ + return await ui.select({ message: "Review your Sentry setup", options: [ { label: "Continue", value: "continue" }, @@ -133,7 +133,7 @@ describe("InkUI prompt telemetry", () => { { label: "Back", value: "back" }, ], }); - return ui.multiselect({ + return await ui.multiselect({ message: "Choose features", options: [{ label: "Tracing", value: "performanceMonitoring" }], }); diff --git a/packages/cli/test/lib/polling.test.ts b/packages/cli/test/lib/polling.test.ts index 017baa11f..9a592f475 100644 --- a/packages/cli/test/lib/polling.test.ts +++ b/packages/cli/test/lib/polling.test.ts @@ -45,7 +45,7 @@ describe("withProgress", () => { rotatingMessages: ["Finding the relevant bits…"], rotationIntervalMs: 4000, }, - async () => deferred.promise, + async () => await deferred.promise, ); await vi.advanceTimersByTimeAsync(4000); @@ -72,7 +72,7 @@ describe("withProgress", () => { rotatingMessages: ["It’s getting there…"], rotationIntervalMs: 4000, }, - async () => deferred.promise, + async () => await deferred.promise, ); await vi.advanceTimersByTimeAsync(4000); diff --git a/packages/cli/test/lib/sentry-client.auth.test.ts b/packages/cli/test/lib/sentry-client.auth.test.ts index 3bb87bf9a..cee9a601f 100644 --- a/packages/cli/test/lib/sentry-client.auth.test.ts +++ b/packages/cli/test/lib/sentry-client.auth.test.ts @@ -252,7 +252,7 @@ describe("authenticated fetch bearer validation", () => { const cached = await getCachedResponse("GET", RESOURCE_URL, { authorization: "Bearer synthetic-token", }); - return cached?.json(); + return await cached?.json(); }) .toEqual({ source: "network" }); expect(requests).toEqual([ diff --git a/packages/mcp-cloudflare/src/server/app.ts b/packages/mcp-cloudflare/src/server/app.ts index 8d6b0f244..46f23b349 100644 --- a/packages/mcp-cloudflare/src/server/app.ts +++ b/packages/mcp-cloudflare/src/server/app.ts @@ -62,7 +62,7 @@ const app = new Hono<{ .get("/", async (c, next) => { const accept = c.req.header("Accept") ?? ""; if (!accept.includes("text/markdown")) { - return next(); + return await next(); } return c.text(generateHomepageMarkdown(getBaseUrl(c)), 200, { "Content-Type": "text/markdown; charset=utf-8", diff --git a/packages/mcp-cloudflare/src/server/index.ts b/packages/mcp-cloudflare/src/server/index.ts index 7337aafdf..dd09fced5 100644 --- a/packages/mcp-cloudflare/src/server/index.ts +++ b/packages/mcp-cloudflare/src/server/index.ts @@ -180,13 +180,13 @@ const wrappedOAuthProvider = { // with no CORS headers so the browser blocks the cross-origin request. if (request.method === "OPTIONS") { if (isPublicMetadataEndpoint(url.pathname)) { - return finalizeResponse( + return await finalizeResponse( request, url, new Response(null, { status: 204 }), ); } - return finalizeResponse( + return await finalizeResponse( request, url, new Response(null, { status: 204 }), @@ -203,7 +203,7 @@ const wrappedOAuthProvider = { // `/mcp...` metadata (including query) stays exact and the origin endpoint // remains 404. if (url.pathname === "/.well-known/oauth-protected-resource") { - return finalizeResponse( + return await finalizeResponse( request, url, new Response("Not Found", { status: 404 }), @@ -214,7 +214,7 @@ const wrappedOAuthProvider = { url.pathname === "/.well-known/oauth-protected-resource/mcp" || url.pathname.startsWith("/.well-known/oauth-protected-resource/mcp/") ) { - return finalizeResponse( + return await finalizeResponse( request, url, createProtectedResourceMetadataResponse(url), @@ -236,7 +236,7 @@ const wrappedOAuthProvider = { ); if (!rateLimitResult.allowed) { - return finalizeResponse( + return await finalizeResponse( request, url, new Response(rateLimitResult.errorMessage, { status: 429 }), @@ -272,7 +272,7 @@ const wrappedOAuthProvider = { activeSpan?.setAttribute(ACCESS_METHOD_ATTRIBUTE, "sentry_access"); if (!sentryBearerAuth.token) { - return finalizeResponse( + return await finalizeResponse( request, url, new Response("Missing or invalid Sentry-Bearer token", { @@ -291,13 +291,14 @@ const wrappedOAuthProvider = { ctx, sentryBearerAuth.token, ); - return finalizeResponse(request, url, response); + return await finalizeResponse(request, url, response); } } // Reject registrations with userinfo-spoofed redirect URIs before the // library stores the client (e.g. host@example.io). if (request.method === "POST" && url.pathname === "/oauth/register") { + let hasUserInfoRedirect = false; try { const body = (await request.clone().json()) as { redirect_uris?: unknown; @@ -305,30 +306,29 @@ const wrappedOAuthProvider = { const redirectUris = Array.isArray(body.redirect_uris) ? body.redirect_uris : []; - if ( - redirectUris.some( - (uri) => typeof uri === "string" && redirectUriHasUserInfo(uri), - ) - ) { - return finalizeResponse( - request, - url, - new Response( - JSON.stringify({ - error: "invalid_redirect_uri", - error_description: - "redirect_uris must not contain a userinfo component", - }), - { - status: 400, - headers: { "Content-Type": "application/json" }, - }, - ), - ); - } + hasUserInfoRedirect = redirectUris.some( + (uri) => typeof uri === "string" && redirectUriHasUserInfo(uri), + ); } catch { // Malformed body — let the library produce its own error. } + if (hasUserInfoRedirect) { + return await finalizeResponse( + request, + url, + new Response( + JSON.stringify({ + error: "invalid_redirect_uri", + error_description: + "redirect_uris must not contain a userinfo component", + }), + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ), + ); + } } // --- Phase 2: Let the OAuth library handle the request --- @@ -399,7 +399,7 @@ const wrappedOAuthProvider = { patchedAuth, url, ); - return finalizeResponse(request, url, patched); + return await finalizeResponse(request, url, patched); }, }; diff --git a/packages/mcp-cloudflare/src/server/lib/approval-dialog.test.ts b/packages/mcp-cloudflare/src/server/lib/approval-dialog.test.ts index 854a6f8db..7030427d1 100644 --- a/packages/mcp-cloudflare/src/server/lib/approval-dialog.test.ts +++ b/packages/mcp-cloudflare/src/server/lib/approval-dialog.test.ts @@ -571,7 +571,7 @@ describe("approval-dialog", () => { cookieSecret: TEST_SECRET, }, ); - return response.text(); + return await response.text(); }; it("should strip javascript: clientUri and render name as plain text", async () => { diff --git a/packages/mcp-cloudflare/src/server/lib/approval-dialog.ts b/packages/mcp-cloudflare/src/server/lib/approval-dialog.ts index 309bd0849..fc4430447 100644 --- a/packages/mcp-cloudflare/src/server/lib/approval-dialog.ts +++ b/packages/mcp-cloudflare/src/server/lib/approval-dialog.ts @@ -41,7 +41,7 @@ async function importKey(secret: string): Promise { ); } const enc = new TextEncoder(); - return crypto.subtle.importKey( + return await crypto.subtle.importKey( "raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, diff --git a/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts b/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts index c884567f0..8ae3cb16b 100644 --- a/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts +++ b/packages/mcp-cloudflare/src/server/lib/mcp-handler.ts @@ -284,7 +284,7 @@ export async function handleSentryBearerMcpRequest( return skills.response; } - return handleAuthenticatedMcpRequest(request, env, ctx, { + return await handleAuthenticatedMcpRequest(request, env, ctx, { kind: "sentry-bearer", accessToken, grantedSkills: skills.grantedSkills, @@ -455,7 +455,7 @@ async function handleAuthenticatedMcpRequest( // The modern handler requires a factory and creates a fresh SDK v2 server // for every request. Context remains request-scoped through tool closures. - return createMcpHandler( + return await createMcpHandler( () => buildServer({ context: serverContext, @@ -591,7 +591,7 @@ const mcpHandler: ExportedHandler = { let upstreamUnauthorizedHandled = false; - return handleAuthenticatedMcpRequest(request, env, ctx, { + return await handleAuthenticatedMcpRequest(request, env, ctx, { kind: "oauth", userId, clientId, diff --git a/packages/mcp-cloudflare/src/server/oauth/callback.test.ts b/packages/mcp-cloudflare/src/server/oauth/callback.test.ts index dd69a614a..9a4808eea 100644 --- a/packages/mcp-cloudflare/src/server/oauth/callback.test.ts +++ b/packages/mcp-cloudflare/src/server/oauth/callback.test.ts @@ -78,7 +78,7 @@ function createTestEnv(): Env { } async function createClient(testEnv: Env, name = "Test Client") { - return testEnv.OAUTH_PROVIDER.createClient({ + return await testEnv.OAUTH_PROVIDER.createClient({ clientName: name, redirectUris: [REDIRECT_URI], tokenEndpointAuthMethod: "none", @@ -106,7 +106,7 @@ async function createSignedCallbackState( exp: now + 10 * 60 * 1000, } as unknown as OAuthState; - return signState(payload, COOKIE_SECRET); + return await signState(payload, COOKIE_SECRET); } async function approveClient( @@ -171,7 +171,7 @@ async function callCallback( url.searchParams.set("error_description", options.errorDescription); } - return oauthApp.fetch( + return await oauthApp.fetch( new Request(url, { method: "GET", headers: options.cookie ? { Cookie: options.cookie } : undefined, diff --git a/packages/mcp-cloudflare/src/server/oauth/routes/authorize.ts b/packages/mcp-cloudflare/src/server/oauth/routes/authorize.ts index 0fe38f453..4bdaf1215 100644 --- a/packages/mcp-cloudflare/src/server/oauth/routes/authorize.ts +++ b/packages/mcp-cloudflare/src/server/oauth/routes/authorize.ts @@ -375,7 +375,7 @@ export default new Hono<{ Bindings: Env }>() }, }); - return redirectToUpstream( + return await redirectToUpstream( c.env, c.req.raw, oauthReqWithSkills, diff --git a/packages/mcp-cloudflare/src/server/oauth/state.ts b/packages/mcp-cloudflare/src/server/oauth/state.ts index a2e11fea3..d36230ede 100644 --- a/packages/mcp-cloudflare/src/server/oauth/state.ts +++ b/packages/mcp-cloudflare/src/server/oauth/state.ts @@ -22,7 +22,7 @@ async function importKey(secret: string): Promise { ); } const enc = new TextEncoder(); - return crypto.subtle.importKey( + return await crypto.subtle.importKey( "raw", enc.encode(secret), { name: "HMAC", hash: "SHA-256" }, diff --git a/packages/mcp-core/src/api-client/client.ts b/packages/mcp-core/src/api-client/client.ts index 24352c1da..91d224cb1 100644 --- a/packages/mcp-core/src/api-client/client.ts +++ b/packages/mcp-core/src/api-client/client.ts @@ -782,7 +782,7 @@ export class SentryApiService { const method = (options.method ?? "GET").toUpperCase(); const isIdempotent = method === "GET"; - return retryWithBackoff( + return await retryWithBackoff( () => this.requestOnce(path, options, requestOptions), { maxRetries: isIdempotent ? RETRYABLE_REQUEST_MAX_RETRIES : 0, @@ -994,7 +994,7 @@ export class SentryApiService { private async parseJsonResponse(response: Response): Promise { // Handle case where response might not have all properties (e.g., in tests or promise chains) if (!response.headers?.get) { - return response.json(); + return await response.json(); } const contentType = response.headers.get("content-type"); @@ -1052,7 +1052,7 @@ export class SentryApiService { requestOptions?: { host?: string }, ): Promise { const response = await this.request(path, options, requestOptions); - return this.parseJsonResponse(response); + return await this.parseJsonResponse(response); } /** Let SDK operations build paths and queries while MCP owns transport and errors. */ @@ -4156,7 +4156,7 @@ export class SentryApiService { }, opts?: RequestOptions, ): Promise { - return this.listIssueLinkPages( + return await this.listIssueLinkPages( apiPath`/organizations/${organizationSlug}/issues/${issueId}/external-issues/`, ExternalIssueListSchema, opts, @@ -4191,7 +4191,7 @@ export class SentryApiService { }: { organizationSlug: string; issueId: string }, opts?: RequestOptions, ): Promise { - return this.listIssueLinkPages( + return await this.listIssueLinkPages( apiPath`/organizations/${organizationSlug}/issues/${issueId}/integrations/`, IssueIntegrationListSchema, opts, @@ -4257,7 +4257,7 @@ export class SentryApiService { { organizationSlug }: { organizationSlug: string }, opts?: RequestOptions, ): Promise { - return this.listIssueLinkPages( + return await this.listIssueLinkPages( apiPath`/organizations/${organizationSlug}/sentry-app-installations/`, SentryAppInstallationListSchema, { ...opts, host: this.isPublicSaas() ? "sentry.io" : opts?.host }, @@ -4268,7 +4268,7 @@ export class SentryApiService { { organizationSlug }: { organizationSlug: string }, opts?: RequestOptions, ): Promise { - return this.listIssueLinkPages( + return await this.listIssueLinkPages( apiPath`/organizations/${organizationSlug}/sentry-app-components/` + "?filter=issue-link", SentryAppComponentListSchema, @@ -4502,7 +4502,7 @@ export class SentryApiService { }, opts?: RequestOptions, ): Promise { - return this.getEventForIssue( + return await this.getEventForIssue( { organizationSlug, issueId, @@ -5914,7 +5914,7 @@ export class SentryApiService { const path = apiPath`/organizations/${organizationSlug}/preprodartifacts/snapshots/${snapshotId}/` + `?${params.toString()}`; - return this.requestJSON(path); + return await this.requestJSON(path); } async getSnapshotImageDetail({ @@ -5927,7 +5927,7 @@ export class SentryApiService { imageIdentifier: string; }): Promise { const path = apiPath`/organizations/${organizationSlug}/preprodartifacts/snapshots/${snapshotId}/images/${imageIdentifier}/`; - return this.requestJSON(path); + return await this.requestJSON(path); } async fetchImageByUrl( @@ -5978,6 +5978,6 @@ export class SentryApiService { const path = apiPath`/organizations/${organizationSlug}/preprodartifacts/snapshots/latest-base/` + `?${params.toString()}`; - return this.requestJSON(path); + return await this.requestJSON(path); } } diff --git a/packages/mcp-core/src/internal/agents/azure-openai-provider.ts b/packages/mcp-core/src/internal/agents/azure-openai-provider.ts index d2c579497..c6c92a28a 100644 --- a/packages/mcp-core/src/internal/agents/azure-openai-provider.ts +++ b/packages/mcp-core/src/internal/agents/azure-openai-provider.ts @@ -79,13 +79,13 @@ function createAzureOpenAIFetch(baseUrl: string): typeof fetch { } if (input instanceof Request) { - return fetch(new Request(requestUrl.toString(), input), { + return await fetch(new Request(requestUrl.toString(), input), { ...init, headers, }); } - return fetch(requestUrl.toString(), { + return await fetch(requestUrl.toString(), { ...init, headers, }); diff --git a/packages/mcp-core/src/internal/agents/tools/dataset-fields.ts b/packages/mcp-core/src/internal/agents/tools/dataset-fields.ts index 21528fe1e..9dbe8f5db 100644 --- a/packages/mcp-core/src/internal/agents/tools/dataset-fields.ts +++ b/packages/mcp-core/src/internal/agents/tools/dataset-fields.ts @@ -169,9 +169,14 @@ export function createDatasetFieldsTool(options: { description: `Discover available fields for ${dataset} searches in Sentry (includes example values)`, parameters: z.object({}), execute: async () => { - return discoverDatasetFields(apiService, organizationSlug, dataset, { - projectId, - }); + return await discoverDatasetFields( + apiService, + organizationSlug, + dataset, + { + projectId, + }, + ); }, getResultCount: (result) => result.fields.length, }); diff --git a/packages/mcp-core/src/tools/catalog-runtime/availability.ts b/packages/mcp-core/src/tools/catalog-runtime/availability.ts index 9d04931cb..dc9c0237c 100644 --- a/packages/mcp-core/src/tools/catalog-runtime/availability.ts +++ b/packages/mcp-core/src/tools/catalog-runtime/availability.ts @@ -243,7 +243,7 @@ export async function executeToolHandler({ }) { const paramsWithConstraints = prepareToolParams({ tool, params, context }); - return tool.handler(paramsWithConstraints as never, context); + return await tool.handler(paramsWithConstraints as never, context); } export function prepareToolParams({ diff --git a/packages/mcp-core/src/tools/catalog/get-issue-details.ts b/packages/mcp-core/src/tools/catalog/get-issue-details.ts index 7c0170065..39749fd2b 100644 --- a/packages/mcp-core/src/tools/catalog/get-issue-details.ts +++ b/packages/mcp-core/src/tools/catalog/get-issue-details.ts @@ -715,7 +715,7 @@ async function maybeFetchAutofixState({ return undefined; } - return apiService + return await apiService .getAutofixState({ organizationSlug, issueId: String(issue.id) }) .catch(() => undefined); } diff --git a/packages/mcp-core/src/tools/catalog/get-replay-details.ts b/packages/mcp-core/src/tools/catalog/get-replay-details.ts index e1a29bbd2..518f75433 100644 --- a/packages/mcp-core/src/tools/catalog/get-replay-details.ts +++ b/packages/mcp-core/src/tools/catalog/get-replay-details.ts @@ -400,7 +400,7 @@ async function fetchReplayIssues({ }): Promise { const ids = errorIds.slice(0, MAX_RELATED_ERRORS); - return Promise.all( + return await Promise.all( ids.map(async (eventId) => { try { const [issue] = await apiService.listIssues({ @@ -427,7 +427,7 @@ async function fetchReplayTraces({ }): Promise { const ids = traceIds.slice(0, MAX_RELATED_TRACES); - return Promise.all( + return await Promise.all( ids.map(async (traceId) => { try { const traceMeta = await apiService.getTraceMeta({ diff --git a/packages/mcp-core/src/tools/catalog/get-sentry-resource.ts b/packages/mcp-core/src/tools/catalog/get-sentry-resource.ts index 287430cd2..91a95e89a 100644 --- a/packages/mcp-core/src/tools/catalog/get-sentry-resource.ts +++ b/packages/mcp-core/src/tools/catalog/get-sentry-resource.ts @@ -597,7 +597,7 @@ export default defineTool({ switch (resolved.type) { case "issue": - return getIssueDetails.handler( + return await getIssueDetails.handler( { organizationSlug: resolved.organizationSlug, issueId: resolved.issueId, @@ -607,7 +607,7 @@ export default defineTool({ ); case "event": - return getIssueDetails.handler( + return await getIssueDetails.handler( { organizationSlug: resolved.organizationSlug, issueId: resolved.issueId, @@ -618,7 +618,7 @@ export default defineTool({ ); case "trace": - return getTraceDetails.handler( + return await getTraceDetails.handler( { organizationSlug: resolved.organizationSlug, traceId: resolved.traceId!, @@ -628,7 +628,7 @@ export default defineTool({ ); case "span": - return getSpanDetails.handler( + return await getSpanDetails.handler( { organizationSlug: resolved.organizationSlug, traceId: resolved.traceId!, @@ -639,7 +639,7 @@ export default defineTool({ ); case "ai_conversation": - return getAIConversationDetails.handler( + return await getAIConversationDetails.handler( { organizationSlug: resolved.organizationSlug, conversationId: resolved.conversationId!, @@ -652,7 +652,7 @@ export default defineTool({ ); case "replay": - return getReplayDetails.handler( + return await getReplayDetails.handler( { replayUrl: params.url, organizationSlug: resolved.organizationSlug, @@ -664,7 +664,7 @@ export default defineTool({ case "monitor": assertCatalogToolAvailable(context, "get_monitor_details", "Monitor"); - return getMonitorDetails.handler( + return await getMonitorDetails.handler( { organizationSlug: resolved.organizationSlug, projectSlugOrId: resolved.projectSlugOrId ?? null, @@ -682,7 +682,7 @@ export default defineTool({ ); case "profile": - return getProfileDetails.handler( + return await getProfileDetails.handler( { profileUrl: params.url, organizationSlug: resolved.organizationSlug, @@ -703,7 +703,7 @@ export default defineTool({ "get_dashboard_details", "Dashboard", ); - return getDashboardDetails.handler( + return await getDashboardDetails.handler( { organizationSlug: resolved.organizationSlug, dashboardIdOrTitle: resolved.dashboardIdOrTitle!, @@ -714,7 +714,7 @@ export default defineTool({ case "snapshot": if (resolved.selectedSnapshot) { - return getSnapshotImage.handler( + return await getSnapshotImage.handler( { organizationSlug: resolved.organizationSlug, snapshotId: resolved.snapshotId!, @@ -726,7 +726,7 @@ export default defineTool({ ); } - return getSnapshot.handler( + return await getSnapshot.handler( { organizationSlug: resolved.organizationSlug, snapshotId: resolved.snapshotId!, diff --git a/packages/mcp-core/src/tools/catalog/get-snapshot-image.ts b/packages/mcp-core/src/tools/catalog/get-snapshot-image.ts index 56fcd0df4..db0f8f060 100644 --- a/packages/mcp-core/src/tools/catalog/get-snapshot-image.ts +++ b/packages/mcp-core/src/tools/catalog/get-snapshot-image.ts @@ -80,7 +80,7 @@ export default defineTool({ regionUrl: params.regionUrl ?? undefined, }); - return fetchSnapshotImage( + return await fetchSnapshotImage( apiService, params.organizationSlug, params.snapshotId, diff --git a/packages/mcp-core/src/tools/catalog/get-snapshot.ts b/packages/mcp-core/src/tools/catalog/get-snapshot.ts index 83aec7136..380b34194 100644 --- a/packages/mcp-core/src/tools/catalog/get-snapshot.ts +++ b/packages/mcp-core/src/tools/catalog/get-snapshot.ts @@ -97,7 +97,7 @@ export default defineTool({ regionUrl: params.regionUrl ?? undefined, }); - return fetchSnapshotSummary( + return await fetchSnapshotSummary( apiService, params.organizationSlug, params.snapshotId, diff --git a/packages/mcp-core/src/tools/catalog/get-span-details.ts b/packages/mcp-core/src/tools/catalog/get-span-details.ts index 1ae46d578..fd9e88ffe 100644 --- a/packages/mcp-core/src/tools/catalog/get-span-details.ts +++ b/packages/mcp-core/src/tools/catalog/get-span-details.ts @@ -43,6 +43,6 @@ export default defineTool({ openWorldHint: true, }, async handler(params, context: ServerContext) { - return getTraceDetails.handler(params, context); + return await getTraceDetails.handler(params, context); }, }); diff --git a/packages/mcp-core/src/tools/catalog/search-agent-conversations.ts b/packages/mcp-core/src/tools/catalog/search-agent-conversations.ts index 87994cfd7..bd3168401 100644 --- a/packages/mcp-core/src/tools/catalog/search-agent-conversations.ts +++ b/packages/mcp-core/src/tools/catalog/search-agent-conversations.ts @@ -96,7 +96,7 @@ async function resolveProjectIds({ return undefined; } - return Promise.all( + return await Promise.all( projects.map(async (projectSlugOrId) => { if (projectSlugOrId === "-1" || isNumericId(projectSlugOrId)) { return projectSlugOrId; diff --git a/packages/mcp-core/src/tools/catalog/search-errors.ts b/packages/mcp-core/src/tools/catalog/search-errors.ts index 521de65af..8953999e0 100644 --- a/packages/mcp-core/src/tools/catalog/search-errors.ts +++ b/packages/mcp-core/src/tools/catalog/search-errors.ts @@ -31,7 +31,7 @@ export default defineTool({ ].join("\n"), inputSchema: buildDatasetSearchInputSchema(), async handler(params, context: ServerContext) { - return runSearchEvents({ ...params, dataset: "errors" }, context, { + return await runSearchEvents({ ...params, dataset: "errors" }, context, { lockDataset: true, }); }, diff --git a/packages/mcp-core/src/tools/catalog/search-events.ts b/packages/mcp-core/src/tools/catalog/search-events.ts index 293c8b027..293d46123 100644 --- a/packages/mcp-core/src/tools/catalog/search-events.ts +++ b/packages/mcp-core/src/tools/catalog/search-events.ts @@ -21,6 +21,6 @@ export default defineTool({ ].join("\n"), inputSchema: buildSearchEventsInputSchema(), async handler(params, context: ServerContext) { - return runSearchEvents(params, context); + return await runSearchEvents(params, context); }, }); diff --git a/packages/mcp-core/src/tools/catalog/search-logs.ts b/packages/mcp-core/src/tools/catalog/search-logs.ts index bd98ca29d..1f17f5a16 100644 --- a/packages/mcp-core/src/tools/catalog/search-logs.ts +++ b/packages/mcp-core/src/tools/catalog/search-logs.ts @@ -32,7 +32,7 @@ export default defineTool({ ].join("\n"), inputSchema: buildDatasetSearchInputSchema(), async handler(params, context: ServerContext) { - return runSearchEvents({ ...params, dataset: "logs" }, context, { + return await runSearchEvents({ ...params, dataset: "logs" }, context, { lockDataset: true, }); }, diff --git a/packages/mcp-core/src/tools/catalog/search-metrics.ts b/packages/mcp-core/src/tools/catalog/search-metrics.ts index 831bead81..8b6807066 100644 --- a/packages/mcp-core/src/tools/catalog/search-metrics.ts +++ b/packages/mcp-core/src/tools/catalog/search-metrics.ts @@ -30,7 +30,7 @@ export default defineTool({ ].join("\n"), inputSchema: buildDatasetSearchInputSchema(), async handler(params, context: ServerContext) { - return runSearchEvents({ ...params, dataset: "metrics" }, context, { + return await runSearchEvents({ ...params, dataset: "metrics" }, context, { lockDataset: true, }); }, diff --git a/packages/mcp-core/src/tools/catalog/search-profiles.ts b/packages/mcp-core/src/tools/catalog/search-profiles.ts index 15c350f8f..d959cfb6d 100644 --- a/packages/mcp-core/src/tools/catalog/search-profiles.ts +++ b/packages/mcp-core/src/tools/catalog/search-profiles.ts @@ -27,7 +27,7 @@ export default defineTool({ ].join("\n"), inputSchema: buildDatasetSearchInputSchema(), async handler(params, context: ServerContext) { - return runSearchEvents({ ...params, dataset: "profiles" }, context, { + return await runSearchEvents({ ...params, dataset: "profiles" }, context, { lockDataset: true, }); }, diff --git a/packages/mcp-core/src/tools/catalog/search-replays.ts b/packages/mcp-core/src/tools/catalog/search-replays.ts index 8d14291c9..455b80ad1 100644 --- a/packages/mcp-core/src/tools/catalog/search-replays.ts +++ b/packages/mcp-core/src/tools/catalog/search-replays.ts @@ -27,7 +27,7 @@ export default defineTool({ ].join("\n"), inputSchema: buildReplaySearchInputSchema(), async handler(params, context: ServerContext) { - return runSearchEvents({ ...params, dataset: "replays" }, context, { + return await runSearchEvents({ ...params, dataset: "replays" }, context, { lockDataset: true, }); }, diff --git a/packages/mcp-core/src/tools/catalog/search-traces.ts b/packages/mcp-core/src/tools/catalog/search-traces.ts index c15efeb22..ef81ded85 100644 --- a/packages/mcp-core/src/tools/catalog/search-traces.ts +++ b/packages/mcp-core/src/tools/catalog/search-traces.ts @@ -33,7 +33,7 @@ export default defineTool({ ].join("\n"), inputSchema: buildDatasetSearchInputSchema(), async handler(params, context: ServerContext) { - return runSearchEvents({ ...params, dataset: "spans" }, context, { + return await runSearchEvents({ ...params, dataset: "spans" }, context, { lockDataset: true, }); }, diff --git a/packages/mcp-core/src/tools/catalog/support/alerts.ts b/packages/mcp-core/src/tools/catalog/support/alerts.ts index 3b2c54951..5352a9782 100644 --- a/packages/mcp-core/src/tools/catalog/support/alerts.ts +++ b/packages/mcp-core/src/tools/catalog/support/alerts.ts @@ -65,7 +65,7 @@ export async function resolveIssueAlertRule( }); if (matches.length === 1) { - return apiService.getIssueAlertRule({ + return await apiService.getIssueAlertRule({ organizationSlug: params.organizationSlug, projectSlug: params.projectSlug, ruleId: matches[0].id, @@ -126,7 +126,7 @@ export async function resolveMetricAlertRule( } monitorId = matches[0].id; } - return getMetricMonitor(apiService, { + return await getMetricMonitor(apiService, { organizationSlug, projectSlug, monitorId, diff --git a/packages/mcp-core/src/tools/special/execute-tool.ts b/packages/mcp-core/src/tools/special/execute-tool.ts index 0952e3ed0..b9c6f4db7 100644 --- a/packages/mcp-core/src/tools/special/execute-tool.ts +++ b/packages/mcp-core/src/tools/special/execute-tool.ts @@ -40,7 +40,7 @@ async function executeCatalogToolWithSpan({ params: Record; context: ServerContext; }) { - return startSpan( + return await startSpan( { name: `tools/call ${tool.name}`, op: "mcp.execute_tool", @@ -135,7 +135,7 @@ export function createExecuteTool(getTools: () => ToolRegistry) { ); } - return executeCatalogToolWithSpan({ + return await executeCatalogToolWithSpan({ tool: match.tool, params: params.arguments, context, diff --git a/packages/mcp-core/src/tools/support/dashboards/index.ts b/packages/mcp-core/src/tools/support/dashboards/index.ts index 28808f4e9..f4e4f8c9e 100644 --- a/packages/mcp-core/src/tools/support/dashboards/index.ts +++ b/packages/mcp-core/src/tools/support/dashboards/index.ts @@ -58,7 +58,7 @@ export async function resolveDashboardProjectConstraint({ return null; } - return apiService.getProject({ + return await apiService.getProject({ organizationSlug, projectSlugOrId: scopedProjectSlug, }); diff --git a/packages/mcp-core/src/tools/support/issue-linking/index.ts b/packages/mcp-core/src/tools/support/issue-linking/index.ts index 0296eab9a..8968720aa 100644 --- a/packages/mcp-core/src/tools/support/issue-linking/index.ts +++ b/packages/mcp-core/src/tools/support/issue-linking/index.ts @@ -248,7 +248,7 @@ export async function linkExternalIssue( input: IssueLinkParams, ): Promise { const { params, url } = validatedParams(input); - if (params.appSlug) return linkAppIssue(apiService, params); + if (params.appSlug) return await linkAppIssue(apiService, params); const candidates = await nativeCandidates(apiService, params, url); if (!candidates.length) { throw new UserInputError( @@ -279,7 +279,7 @@ export async function unlinkExternalIssue( input: IssueLinkParams, ): Promise { const { params, url } = validatedParams(input); - if (params.appSlug) return unlinkAppIssue(apiService, params); + if (params.appSlug) return await unlinkAppIssue(apiService, params); // Existing associations can outlive a disabled or reconfigured installation. // Match their stored URL, without applying link-time installation eligibility. const integrations = await apiService.listIssueIntegrations(params); @@ -317,7 +317,7 @@ export async function unlinkExternalIssue( const match = matches[0]; if (!match) { // Custom Apps can own any URL, including one with a native provider shape. - if (!params.integrationId) return unlinkAppIssue(apiService, params); + if (!params.integrationId) return await unlinkAppIssue(apiService, params); return { url: params.externalIssueUrl, status: "not_linked" }; } await apiService.unlinkNativeExternalIssue({ diff --git a/packages/mcp-core/src/tools/support/search-events/utils.ts b/packages/mcp-core/src/tools/support/search-events/utils.ts index 4ba5ab06a..9c18b2495 100644 --- a/packages/mcp-core/src/tools/support/search-events/utils.ts +++ b/packages/mcp-core/src/tools/support/search-events/utils.ts @@ -1050,7 +1050,7 @@ export async function validateEventsSearch( end?: string; }, ): Promise { - return apiService.validateEvents({ + return await apiService.validateEvents({ organizationSlug, dataset, fields, diff --git a/packages/mcp-server/src/cli/commands/auth.ts b/packages/mcp-server/src/cli/commands/auth.ts index 3630951a1..3ddee7834 100644 --- a/packages/mcp-server/src/cli/commands/auth.ts +++ b/packages/mcp-server/src/cli/commands/auth.ts @@ -94,11 +94,11 @@ export async function authCommand(argv: string[]): Promise { switch (sub) { case "login": - return login(rest); + return await login(rest); case "logout": - return logout(rest); + return await logout(rest); case "status": - return status(rest); + return await status(rest); default: console.error(`Unknown auth command: ${sub}`); console.error("Available: auth login, auth logout, auth status"); diff --git a/packages/mcp-test-client/src/auth/oauth.ts b/packages/mcp-test-client/src/auth/oauth.ts index e834f9b10..b7358863e 100644 --- a/packages/mcp-test-client/src/auth/oauth.ts +++ b/packages/mcp-test-client/src/auth/oauth.ts @@ -191,7 +191,7 @@ export class OAuthClient { private async startCallbackServer(): Promise<{ waitForCallback: () => Promise<{ code: string; state: string }>; }> { - return new Promise((resolve, reject) => { + return await new Promise((resolve, reject) => { let resolveCallback: | ((value: { code: string; state: string }) => void) | null = null; @@ -329,7 +329,7 @@ export class OAuthClient { throw new Error(`Token exchange failed: ${response.status} - ${error}`); } - return response.json() as Promise; + return await (response.json() as Promise); } /** @@ -387,7 +387,7 @@ export class OAuthClient { } // No cached token, perform OAuth flow - return this.authenticate(); + return await this.authenticate(); } /** diff --git a/packages/toolkit-core/src/region-cache.ts b/packages/toolkit-core/src/region-cache.ts index 8319763a0..adb31b65c 100644 --- a/packages/toolkit-core/src/region-cache.ts +++ b/packages/toolkit-core/src/region-cache.ts @@ -15,7 +15,7 @@ export function resolveCachedRegion( // Start before returning so the lookup sees the source identity that keyed // this cache entry, even if a caller changes credentials in the same tick. - const pending = (async () => lookup())() + const pending = (async () => await lookup())() .then((value) => { if (!isCacheable(value) && cache.get(key) === pending) { cache.delete(key); diff --git a/scripts/cli-nightly-watchdog.mjs b/scripts/cli-nightly-watchdog.mjs index 04961e9d5..3c90c046b 100644 --- a/scripts/cli-nightly-watchdog.mjs +++ b/scripts/cli-nightly-watchdog.mjs @@ -77,7 +77,7 @@ async function getJson(url, headers) { if (!response.ok) { throw new Error(`GET ${url} returned ${response.status}`); } - return response.json(); + return await response.json(); } async function readNightlyVersion(repository) { diff --git a/scripts/oxc-config.test.mjs b/scripts/oxc-config.test.mjs index b1340a450..6d8ed0804 100644 --- a/scripts/oxc-config.test.mjs +++ b/scripts/oxc-config.test.mjs @@ -70,16 +70,3 @@ test("CI lint checks formatting for commits that skip the pre-commit hook", () = assert.match(scripts.lint, /pnpm run lint:format/); assert.match(workflow, /^\s*- run: pnpm run lint$/m); }); - -test("CLI sources require awaited returned promises via type-aware Oxlint", () => { - const config = readRootJson(".oxlintrc.json"); - const packageJson = readRootJson("package.json"); - const cli = config.overrides.find( - ({ files }) => - files.length === 1 && files[0] === "packages/cli/src/**/*.{ts,tsx}", - ); - - assert.equal(config.options.typeAware, true); - assert.ok(packageJson.dependencies["oxlint-tsgolint"]); - assert.deepEqual(cli.rules["typescript/return-await"], ["error", "always"]); -}); From 331f427b8b3f489952f352f07ff26aa691de4f62 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Wed, 21 Oct 2026 14:18:46 +0000 Subject: [PATCH 3/3] chore: Forbid async callbacks in .catch() Add a repo-wide ast-grep rule, no-async-catch-callback, so the `.catch(async (error) => ...)` pattern replaced by withDashboardErrors can't come back, and note it in the coding guidelines. Co-authored-by: byk Co-Authored-By: Claude --- .../no-async-catch-callback-test.yml | 27 +++++++++++++++++++ ast-grep-rules/no-async-catch-callback.yml | 13 +++++++++ docs/contributing/coding-guidelines.md | 3 +++ 3 files changed, 43 insertions(+) create mode 100644 ast-grep-rule-tests/no-async-catch-callback-test.yml create mode 100644 ast-grep-rules/no-async-catch-callback.yml diff --git a/ast-grep-rule-tests/no-async-catch-callback-test.yml b/ast-grep-rule-tests/no-async-catch-callback-test.yml new file mode 100644 index 000000000..3ad0a44d9 --- /dev/null +++ b/ast-grep-rule-tests/no-async-catch-callback-test.yml @@ -0,0 +1,27 @@ +id: no-async-catch-callback +valid: + - | + const value = await getDashboard(org, id).catch((error) => { + throw wrap(error); + }); + - | + try { + return await getDashboard(org, id); + } catch (error) { + return await enrichDashboardError(error, ctx); + } + - | + await withDashboardErrors(ctx, () => getDashboard(org, id)); +invalid: + - | + const value = await getDashboard(org, id).catch( + async (error: unknown) => await enrichDashboardError(error, ctx), + ); + - | + await withProgress(options, fn).catch(async (error) => { + await report(error); + }); + - | + run().catch(async function (error) { + await report(error); + }); diff --git a/ast-grep-rules/no-async-catch-callback.yml b/ast-grep-rules/no-async-catch-callback.yml new file mode 100644 index 000000000..2a7eb218e --- /dev/null +++ b/ast-grep-rules/no-async-catch-callback.yml @@ -0,0 +1,13 @@ +id: no-async-catch-callback +language: TypeScript +severity: error +message: Do not pass an async callback to `.catch()`. +note: Use `try { return await op(); } catch (error) { ... }` (or a wrapper such as `withDashboardErrors`) so the error path is awaited explicitly and stays in async stack traces. +rule: + pattern: $PROMISE.catch($HANDLER) +constraints: + HANDLER: + any: + - kind: arrow_function + - kind: function_expression + regex: ^async\b diff --git a/docs/contributing/coding-guidelines.md b/docs/contributing/coding-guidelines.md index 292e43391..33a808fa7 100644 --- a/docs/contributing/coding-guidelines.md +++ b/docs/contributing/coding-guidelines.md @@ -35,6 +35,9 @@ product-specific implementation patterns. write `return await` in async functions so callers stay in async stack traces. Type-aware correctness rules that still have a backlog are turned off at the root; #1573 tracks enabling them. +- Don't pass an async callback to `.catch()`. Use `try`/`await`/`catch`, or a + wrapper such as `withDashboardErrors(ctx, fn)`. The `no-async-catch-callback` + ast-grep rule enforces this. - VS Code uses the Oxc extension for formatting and linting. Run `pnpm exec oxfmt --write ` to format a file.