From 2f2b4bd36fd9a4537c117c9dc8b34a0c632fa7d4 Mon Sep 17 00:00:00 2001 From: "sentry[bot]" <39604003+sentry[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 22:47:40 +0000 Subject: [PATCH 1/2] fix(error-reporting): Silence SeerError as feature_disabled --- packages/cli/src/lib/error-reporting.ts | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/packages/cli/src/lib/error-reporting.ts b/packages/cli/src/lib/error-reporting.ts index a44c98596..a4fb4de1e 100644 --- a/packages/cli/src/lib/error-reporting.ts +++ b/packages/cli/src/lib/error-reporting.ts @@ -54,7 +54,8 @@ type SilenceReason = | "output_error" | "auth_expected" | "api_user_error" - | "network_error"; + | "network_error" + | "feature_disabled"; /** * Classify whether an error should be silenced. @@ -94,6 +95,12 @@ export function classifySilenced(error: unknown): SilenceReason | null { if (error instanceof ApiError && error.status > 400 && error.status < 500) { return "api_user_error"; } + // SeerError means the Seer feature is not available for this org (not enabled, + // AI features disabled, or no budget). These are expected feature-gate + // conditions — not CLI bugs — so they should not surface as actionable issues. + if (error instanceof SeerError) { + return "feature_disabled"; + } // A 400 (Bad Request) signals a malformed request the CLI built — a code // defect — so it is always captured. A user's unparseable `--query` is NOT a // 400 here: it is converted to a ValidationError at the command boundary From fcdef36869f38a50df442fd39529d66e12029aff Mon Sep 17 00:00:00 2001 From: "sentry[bot]" <39604003+sentry[bot]@users.noreply.github.com> Date: Fri, 28 Aug 2026 22:53:07 +0000 Subject: [PATCH 2/2] fix(error-reporting): Silence SeerError as feature_disabled --- packages/cli/test/lib/error-reporting.test.ts | 23 +++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/packages/cli/test/lib/error-reporting.test.ts b/packages/cli/test/lib/error-reporting.test.ts index 1b0bed9e1..7acd7fecc 100644 --- a/packages/cli/test/lib/error-reporting.test.ts +++ b/packages/cli/test/lib/error-reporting.test.ts @@ -304,12 +304,19 @@ describe("classifySilenced", () => { new ResolutionError("Project 'x'", "not found", "sentry issue list"), ], ["ValidationError", new ValidationError("bad")], - ["SeerError", new SeerError("not_enabled")], ["ConfigError", new ConfigError("bad")], ["generic Error", new Error("boom")], ])("does NOT silence %s", (_label, err) => { expect(classifySilenced(err)).toBeNull(); }); + + test.each([ + ["not_enabled", new SeerError("not_enabled")], + ["no_budget", new SeerError("no_budget")], + ["ai_disabled", new SeerError("ai_disabled")], + ])("silences SeerError(%s) as feature_disabled", (_label, err) => { + expect(classifySilenced(err)).toBe("feature_disabled"); + }); }); // --------------------------------------------------------------------------- @@ -518,10 +525,18 @@ describe("reportCliError integration", () => { expect(captureSpy).toHaveBeenCalledWith(err); }); - test("captures SeerError (marketing dashboard)", () => { + test("silences SeerError and emits metric", () => { reportCliError(new SeerError("not_enabled", "my-org")); - expect(captureSpy).toHaveBeenCalled(); - expect(metricSpy).not.toHaveBeenCalled(); + expect(captureSpy).not.toHaveBeenCalled(); + expect(metricSpy).toHaveBeenCalledWith( + "cli.error.silenced", + 1, + expect.objectContaining({ + attributes: expect.objectContaining({ + reason: "feature_disabled", + }), + }) + ); }); test("silences AuthError(invalid) and emits metric", () => {