From 477663faee7e3313c40b499ac9ad355b5393759a Mon Sep 17 00:00:00 2001 From: betegon Date: Mon, 7 Sep 2026 16:41:59 +0200 Subject: [PATCH 1/2] fix(init): stop recording skipped verification as an error Expected skips (no dev command, bind/spawn failure) were captured as ERROR events on otherwise healthy completed runs. Co-authored-by: Cursor --- packages/cli/src/lib/init/verify-setup.ts | 34 ++++++++++++---- .../init/verify-setup-windows.mocked.test.ts | 2 + .../cli/test/lib/init/verify-setup.test.ts | 39 ++++++++++++++++++- 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/packages/cli/src/lib/init/verify-setup.ts b/packages/cli/src/lib/init/verify-setup.ts index bf315a8f6..3d112a0b0 100644 --- a/packages/cli/src/lib/init/verify-setup.ts +++ b/packages/cli/src/lib/init/verify-setup.ts @@ -14,7 +14,11 @@ import { type ChildProcess, spawn, spawnSync } from "node:child_process"; import { resolve } from "node:path"; -import { captureException } from "@sentry/node-core/light"; +import { + addBreadcrumb, + captureException, + setTag, +} from "@sentry/node-core/light"; import { createSpotlightBuffer } from "@spotlightjs/spotlight/sdk"; import { BUFFER_SIZE, shutdownServer } from "../../commands/local/run.js"; import { buildApp, tryListen } from "../../commands/local/server.js"; @@ -320,6 +324,24 @@ async function cleanupProcessTree(child: ChildProcess): Promise { } } +type VerificationSkipReason = + | "no_dev_command" + | "server_bind" + | "spawn_failed"; + +/** + * Expected verification skips are not failures. Emitting them with + * `captureException` produced ERROR events on healthy completed runs. + */ +function recordVerificationSkipped(reason: VerificationSkipReason): void { + addBreadcrumb({ + category: "wizard.verify", + level: "info", + message: `skipped:${reason}`, + }); + setTag("wizard.verify", "skipped"); +} + /** * Outcome of {@link verifySetup}, surfaced to the completion screen so it can * celebrate a received event (and deep-link it) instead of only telling the @@ -352,12 +374,7 @@ export async function verifySetup( const detected = await detectDevCommand(cwd); if (!detected) { ui.log.info("Skipping verification — could not detect a dev command"); - captureException(new Error("init verification skipped"), { - tags: { - "wizard.platform": String(result.result?.platform ?? "unknown"), - "wizard.verify": "no_dev_command", - }, - }); + recordVerificationSkipped("no_dev_command"); return { verified: false, kind: "skipped" }; } @@ -375,6 +392,7 @@ export async function verifySetup( } catch (error) { logger.debug("Failed to start verification server", error); ui.log.warn("Skipping verification — could not start local server."); + recordVerificationSkipped("server_bind"); return { verified: false, kind: "skipped" }; } @@ -419,6 +437,7 @@ export async function verifySetup( logger.debug("Failed to spawn verification child", error); await shutdownServer(server); ui.log.warn("Skipping verification — could not start the dev command."); + recordVerificationSkipped("spawn_failed"); return { verified: false, kind: "skipped" }; } @@ -558,6 +577,7 @@ function reportOutcome(outcome: VerifyOutcome, ctx: ReportContext): void { if (outcome.kind === "spawn_error") { logger.debug("Failed to spawn verification child", outcome.error); ui.log.warn("Skipping verification — could not start the dev command."); + recordVerificationSkipped("spawn_failed"); return; } diff --git a/packages/cli/test/lib/init/verify-setup-windows.mocked.test.ts b/packages/cli/test/lib/init/verify-setup-windows.mocked.test.ts index d0655ec2e..0ed40fb5d 100644 --- a/packages/cli/test/lib/init/verify-setup-windows.mocked.test.ts +++ b/packages/cli/test/lib/init/verify-setup-windows.mocked.test.ts @@ -68,7 +68,9 @@ vi.mock("node:child_process", async (importOriginal) => { }); vi.mock("@sentry/node-core/light", () => ({ + addBreadcrumb: vi.fn(), captureException: vi.fn(), + setTag: vi.fn(), })); import { verifySetup } from "../../../src/lib/init/verify-setup.js"; diff --git a/packages/cli/test/lib/init/verify-setup.test.ts b/packages/cli/test/lib/init/verify-setup.test.ts index cc7e42f14..fb9d29bcf 100644 --- a/packages/cli/test/lib/init/verify-setup.test.ts +++ b/packages/cli/test/lib/init/verify-setup.test.ts @@ -5,10 +5,14 @@ import { verifySetup } from "../../../src/lib/init/verify-setup.js"; import { TEST_TMP_DIR } from "../../constants.js"; import { createMockUI } from "./ui/mock-ui.js"; -vi.mock("@sentry/node-core/light", () => ({ +const sentryMocks = vi.hoisted(() => ({ + addBreadcrumb: vi.fn(), captureException: vi.fn(), + setTag: vi.fn(), })); +vi.mock("@sentry/node-core/light", () => sentryMocks); + type FixtureProcesses = { shellPid: number; parentPid: number; @@ -19,6 +23,9 @@ let tmpDir: string; let fixtureProcesses: FixtureProcesses | undefined; beforeEach(async () => { + sentryMocks.addBreadcrumb.mockClear(); + sentryMocks.captureException.mockClear(); + sentryMocks.setTag.mockClear(); tmpDir = await mkdtemp(join(TEST_TMP_DIR, "verify-setup-test-")); }); @@ -133,6 +140,29 @@ async function readFixtureProcesses(): Promise { } describe("verifySetup", () => { + test("records a skipped verify as a scope tag, not an error, when there is no dev command", async () => { + const { ui, calls } = createMockUI(); + + const result = await verifySetup( + { status: "success", result: { platform: "cocoa" } }, + ui, + tmpDir + ); + + expect(result).toEqual({ verified: false, kind: "skipped" }); + expect(calls).toContainEqual({ + kind: "log.info", + message: "Skipping verification — could not detect a dev command", + }); + expect(sentryMocks.captureException).not.toHaveBeenCalled(); + expect(sentryMocks.setTag).toHaveBeenCalledWith("wizard.verify", "skipped"); + expect(sentryMocks.addBreadcrumb).toHaveBeenCalledWith({ + category: "wizard.verify", + level: "info", + message: "skipped:no_dev_command", + }); + }); + test("does not fail init when the detected command cannot be spawned", async () => { await writeFile( join(tmpDir, "package.json"), @@ -153,6 +183,13 @@ describe("verifySetup", () => { kind: "log.warn", message: "Skipping verification — could not start the dev command.", }); + expect(sentryMocks.captureException).not.toHaveBeenCalled(); + expect(sentryMocks.setTag).toHaveBeenCalledWith("wizard.verify", "skipped"); + expect(sentryMocks.addBreadcrumb).toHaveBeenCalledWith({ + category: "wizard.verify", + level: "info", + message: "skipped:spawn_failed", + }); }); test.skipIf(process.platform === "win32")( From 816b4d6b8b3392326ca1769da226038d1f4abe6c Mon Sep 17 00:00:00 2001 From: betegon Date: Mon, 7 Sep 2026 16:45:36 +0200 Subject: [PATCH 2/2] fix(init): format VerificationSkipReason for biome Co-authored-by: Cursor --- packages/cli/src/lib/init/verify-setup.ts | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/packages/cli/src/lib/init/verify-setup.ts b/packages/cli/src/lib/init/verify-setup.ts index 3d112a0b0..e52bf3f11 100644 --- a/packages/cli/src/lib/init/verify-setup.ts +++ b/packages/cli/src/lib/init/verify-setup.ts @@ -324,10 +324,7 @@ async function cleanupProcessTree(child: ChildProcess): Promise { } } -type VerificationSkipReason = - | "no_dev_command" - | "server_bind" - | "spawn_failed"; +type VerificationSkipReason = "no_dev_command" | "server_bind" | "spawn_failed"; /** * Expected verification skips are not failures. Emitting them with