From 7c8c62b65cf8611841fe5bd69a141f8ac395b716 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Thu, 12 Nov 2026 17:26:42 +0000 Subject: [PATCH 1/2] fix(mcp): Explain Seer setup 409s in analyze_issue_with_seer Sentry refuses to start an Autofix run with a 409 and a code (scm_integration_required or repos_not_linked) when the org has no GitHub/GitLab integration or the issue's project has no linked repositories. The tool rethrew that 409, so agents saw a generic "Input Error" telling them to fix their input and retry. Return "Analysis Not Started" with Sentry's explanation instead, like the 402 out-of-credits case, and name the prerequisite in the tool hints. Other 409s still throw. Co-Authored-By: Claude --- .../src/internal/tool-helpers/seer.ts | 27 ++++++ packages/mcp-core/src/skillDefinitions.json | 2 +- packages/mcp-core/src/toolDefinitions.json | 2 +- .../catalog/analyze-issue-with-seer.test.ts | 83 +++++++++++++++++++ .../tools/catalog/analyze-issue-with-seer.ts | 11 +++ 5 files changed, 123 insertions(+), 2 deletions(-) diff --git a/packages/mcp-core/src/internal/tool-helpers/seer.ts b/packages/mcp-core/src/internal/tool-helpers/seer.ts index 608eab2a8..f7cc0d7e9 100644 --- a/packages/mcp-core/src/internal/tool-helpers/seer.ts +++ b/packages/mcp-core/src/internal/tool-helpers/seer.ts @@ -110,6 +110,33 @@ export function getSeerPaymentRequiredMessage(responseBody: unknown): string { return SEER_NO_QUOTA_MESSAGE; } +const SEER_SETUP_REQUIRED_CODES = new Set([ + "scm_integration_required", + "repos_not_linked", +]); + +/** + * Explain a 409 from starting a run when Seer is not set up for the issue's + * project. Returns null for any other 409, such as a run already in flight. + */ +export function getSeerSetupRequiredMessage( + responseBody: unknown, +): string | null { + if ( + !isPlainObject(responseBody) || + typeof responseBody.code !== "string" || + !SEER_SETUP_REQUIRED_CODES.has(responseBody.code) || + typeof responseBody.detail !== "string" + ) { + return null; + } + return [ + responseBody.detail, + "", + "Retrying won't help until someone sets this up in Sentry. Investigate without Seer by reading the issue and its events.", + ].join("\n"); +} + /** * Explain why a failed run stopped, when Seer gives a reason we can explain. */ diff --git a/packages/mcp-core/src/skillDefinitions.json b/packages/mcp-core/src/skillDefinitions.json index 8c9257566..35403f5e4 100644 --- a/packages/mcp-core/src/skillDefinitions.json +++ b/packages/mcp-core/src/skillDefinitions.json @@ -259,7 +259,7 @@ "tools": [ { "name": "analyze_issue_with_seer", - "description": "Use Seer to analyze production errors and get detailed root cause analysis with specific code fixes.\n\nUse this tool when:\n- The user explicitly asks for root cause analysis, Seer analysis, or help fixing/debugging an issue\n- You are unable to accurately determine the root cause from the issue details alone\n\nDo NOT call this tool as an automatic follow-up to get_sentry_resource.\n\nWhat this tool provides:\n- Root cause analysis with code-level explanations\n- Specific file locations and line numbers where errors occur\n- Concrete code fixes you can apply\n- Step-by-step implementation guidance\n\nThis tool automatically:\n1. Checks if analysis already exists (instant results)\n2. Starts new AI analysis if needed (~2-5 minutes)\n3. Returns complete fix recommendations\n\n\n### User: \"Run Seer on this issue\"\n\n```\nanalyze_issue_with_seer(issueUrl='https://my-org.sentry.io/issues/PROJECT-1Z43')\n```\n\n### User: \"Analyze this issue and suggest a fix\"\n\n```\nanalyze_issue_with_seer(organizationSlug='my-organization', issueId='ERROR-456')\n```\n\n\n\n- Only use when the user explicitly requests analysis or you cannot determine the root cause from issue details alone\n- Seer Autofix does not support metric alert issues (issueCategory: metric); use get_issue_details and search_metrics or search_traces instead\n- If the user provides an issueUrl, extract it and use that parameter alone\n- The analysis includes actual code snippets and fixes, not just error descriptions\n- Results are cached - subsequent calls return instantly\n", + "description": "Use Seer to analyze production errors and get detailed root cause analysis with specific code fixes.\n\nUse this tool when:\n- The user explicitly asks for root cause analysis, Seer analysis, or help fixing/debugging an issue\n- You are unable to accurately determine the root cause from the issue details alone\n\nDo NOT call this tool as an automatic follow-up to get_sentry_resource.\n\nWhat this tool provides:\n- Root cause analysis with code-level explanations\n- Specific file locations and line numbers where errors occur\n- Concrete code fixes you can apply\n- Step-by-step implementation guidance\n\nThis tool automatically:\n1. Checks if analysis already exists (instant results)\n2. Starts new AI analysis if needed (~2-5 minutes)\n3. Returns complete fix recommendations\n\n\n### User: \"Run Seer on this issue\"\n\n```\nanalyze_issue_with_seer(issueUrl='https://my-org.sentry.io/issues/PROJECT-1Z43')\n```\n\n### User: \"Analyze this issue and suggest a fix\"\n\n```\nanalyze_issue_with_seer(organizationSlug='my-organization', issueId='ERROR-456')\n```\n\n\n\n- Only use when the user explicitly requests analysis or you cannot determine the root cause from issue details alone\n- Seer Autofix does not support metric alert issues (issueCategory: metric); use get_issue_details and search_metrics or search_traces instead\n- Starting a new analysis requires a GitHub or GitLab integration and repositories connected to the issue's project; without them the tool reports that no analysis started\n- If the user provides an issueUrl, extract it and use that parameter alone\n- The analysis includes actual code snippets and fixes, not just error descriptions\n- Results are cached - subsequent calls return instantly\n", "requiredScopes": [] }, { diff --git a/packages/mcp-core/src/toolDefinitions.json b/packages/mcp-core/src/toolDefinitions.json index 85faf0a6a..2c25ba7a2 100644 --- a/packages/mcp-core/src/toolDefinitions.json +++ b/packages/mcp-core/src/toolDefinitions.json @@ -152,7 +152,7 @@ { "name": "analyze_issue_with_seer", "title": "Analyze Issue with Seer", - "description": "Use Seer to analyze production errors and get detailed root cause analysis with specific code fixes.\n\nUse this tool when:\n- The user explicitly asks for root cause analysis, Seer analysis, or help fixing/debugging an issue\n- You are unable to accurately determine the root cause from the issue details alone\n\nDo NOT call this tool as an automatic follow-up to get_sentry_resource.\n\nWhat this tool provides:\n- Root cause analysis with code-level explanations\n- Specific file locations and line numbers where errors occur\n- Concrete code fixes you can apply\n- Step-by-step implementation guidance\n\nThis tool automatically:\n1. Checks if analysis already exists (instant results)\n2. Starts new AI analysis if needed (~2-5 minutes)\n3. Returns complete fix recommendations\n\n\n### User: \"Run Seer on this issue\"\n\n```\nanalyze_issue_with_seer(issueUrl='https://my-org.sentry.io/issues/PROJECT-1Z43')\n```\n\n### User: \"Analyze this issue and suggest a fix\"\n\n```\nanalyze_issue_with_seer(organizationSlug='my-organization', issueId='ERROR-456')\n```\n\n\n\n- Only use when the user explicitly requests analysis or you cannot determine the root cause from issue details alone\n- Seer Autofix does not support metric alert issues (issueCategory: metric); use get_issue_details and search_metrics or search_traces instead\n- If the user provides an issueUrl, extract it and use that parameter alone\n- The analysis includes actual code snippets and fixes, not just error descriptions\n- Results are cached - subsequent calls return instantly\n", + "description": "Use Seer to analyze production errors and get detailed root cause analysis with specific code fixes.\n\nUse this tool when:\n- The user explicitly asks for root cause analysis, Seer analysis, or help fixing/debugging an issue\n- You are unable to accurately determine the root cause from the issue details alone\n\nDo NOT call this tool as an automatic follow-up to get_sentry_resource.\n\nWhat this tool provides:\n- Root cause analysis with code-level explanations\n- Specific file locations and line numbers where errors occur\n- Concrete code fixes you can apply\n- Step-by-step implementation guidance\n\nThis tool automatically:\n1. Checks if analysis already exists (instant results)\n2. Starts new AI analysis if needed (~2-5 minutes)\n3. Returns complete fix recommendations\n\n\n### User: \"Run Seer on this issue\"\n\n```\nanalyze_issue_with_seer(issueUrl='https://my-org.sentry.io/issues/PROJECT-1Z43')\n```\n\n### User: \"Analyze this issue and suggest a fix\"\n\n```\nanalyze_issue_with_seer(organizationSlug='my-organization', issueId='ERROR-456')\n```\n\n\n\n- Only use when the user explicitly requests analysis or you cannot determine the root cause from issue details alone\n- Seer Autofix does not support metric alert issues (issueCategory: metric); use get_issue_details and search_metrics or search_traces instead\n- Starting a new analysis requires a GitHub or GitLab integration and repositories connected to the issue's project; without them the tool reports that no analysis started\n- If the user provides an issueUrl, extract it and use that parameter alone\n- The analysis includes actual code snippets and fixes, not just error descriptions\n- Results are cached - subsequent calls return instantly\n", "inputSchema": { "type": "object", "properties": { diff --git a/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.test.ts b/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.test.ts index 006d26040..a2180251b 100644 --- a/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.test.ts +++ b/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.test.ts @@ -500,6 +500,89 @@ describe("analyze_issue_with_seer", () => { }, ); + it.each([ + [ + "repos_not_linked", + "Seer Autofix requires repositories to be connected to this project before a new run can be started.", + ], + [ + "scm_integration_required", + "Seer Autofix requires a supported SCM integration (GitHub or GitLab) to be installed for your organization before a new run can be started.", + ], + ])( + "explains why the run did not start when Sentry answers 409 (%s)", + async (code, detail) => { + let getCallCount = 0; + + mswServer.use( + http.get( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-409/autofix/", + () => { + getCallCount++; + return HttpResponse.json({ autofix: null }); + }, + ), + http.post( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-409/autofix/", + () => HttpResponse.json({ detail, code }, { status: 409 }), + ), + ); + + const result = await analyzeIssueWithSeer.handler( + { + organizationSlug: "sentry-mcp-evals", + regionUrl: null, + issueId: "CLOUDFLARE-MCP-409", + instruction: undefined, + }, + { + constraints: { organizationSlug: undefined }, + accessToken: "access-token", + userId: "1", + }, + ); + + expect(result).toBe( + `# Seer Analysis for Issue CLOUDFLARE-MCP-409\n\n## Analysis Not Started\n\n${detail}\n\nRetrying won't help until someone sets this up in Sentry. Investigate without Seer by reading the issue and its events.\n`, + ); + // No polling after a refused start. + expect(getCallCount).toBe(1); + }, + ); + + it("still throws a 409 that is not a setup code", async () => { + mswServer.use( + http.get( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-409-OTHER/autofix/", + () => HttpResponse.json({ autofix: null }), + ), + http.post( + "https://sentry.io/api/0/organizations/sentry-mcp-evals/issues/CLOUDFLARE-MCP-409-OTHER/autofix/", + () => + HttpResponse.json( + { detail: "Something else conflicted" }, + { status: 409 }, + ), + ), + ); + + await expect( + analyzeIssueWithSeer.handler( + { + organizationSlug: "sentry-mcp-evals", + regionUrl: null, + issueId: "CLOUDFLARE-MCP-409-OTHER", + instruction: undefined, + }, + { + constraints: { organizationSlug: undefined }, + accessToken: "access-token", + userId: "1", + }, + ), + ).rejects.toThrow("Something else conflicted"); + }); + it("explains a run that stopped because the org ran out of credits", async () => { mswServer.use( http.get( diff --git a/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.ts b/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.ts index 69ba1d20e..afdbfbdf3 100644 --- a/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.ts +++ b/packages/mcp-core/src/tools/catalog/analyze-issue-with-seer.ts @@ -13,6 +13,7 @@ import { getHumanInterventionGuidance, getOutputForAutofixRun, getSeerPaymentRequiredMessage, + getSeerSetupRequiredMessage, getSeerUnsupportedIssueMessage, getStatusDisplayName, isSeerSupportedIssue, @@ -73,6 +74,7 @@ export default defineTool({ "", "- Only use when the user explicitly requests analysis or you cannot determine the root cause from issue details alone", "- Seer Autofix does not support metric alert issues (issueCategory: metric); use get_issue_details and search_metrics or search_traces instead", + "- Starting a new analysis requires a GitHub or GitLab integration and repositories connected to the issue's project; without them the tool reports that no analysis started", "- If the user provides an issueUrl, extract it and use that parameter alone", "- The analysis includes actual code snippets and fixes, not just error descriptions", "- Results are cached - subsequent calls return instantly", @@ -157,6 +159,15 @@ export default defineTool({ output += `## Analysis Not Started\n\n${getSeerPaymentRequiredMessage(error.responseBody)}\n`; return output; } + // Sentry answers 409 with a setup code when the org has no SCM + // integration or the project has no linked repositories. + if (error instanceof ApiError && error.status === 409) { + const setupMessage = getSeerSetupRequiredMessage(error.responseBody); + if (setupMessage) { + output += `## Analysis Not Started\n\n${setupMessage}\n`; + return output; + } + } throw error; } output += `Starting new analysis...\n\n`; From 9c6b6e2519ee6b93aefefff9f18cd7b243e815f7 Mon Sep 17 00:00:00 2001 From: "sentry-junior[bot]" <264270552+sentry-junior[bot]@users.noreply.github.com> Date: Thu, 12 Nov 2026 21:52:39 +0000 Subject: [PATCH 2/2] fix(cli): Explain Seer setup 409s in issue explain and plan Sentry refuses to start an Autofix run with a 409 and code scm_integration_required or repos_not_linked. The CLI passed that through as a generic ApiError (exit 30). Map both codes to SeerError, which says what's missing and links the integrations or Seer settings page, and give them exit code 44 (SEER_SETUP_REQUIRED). Other 409s, such as a run already in flight, stay ApiErrors. Co-Authored-By: Claude --- apps/cli-docs/src/content/docs/exit-codes.md | 1 + packages/cli/AGENTS.md | 2 +- packages/cli/src/lib/errors.ts | 19 ++++++++++++- packages/cli/src/lib/formatters/seer.ts | 22 ++++++++++++--- .../test/commands/issue/explain.func.test.ts | 24 +++++++++++++++- packages/cli/test/lib/errors.test.ts | 28 +++++++++++++++++++ packages/cli/test/lib/formatters/seer.test.ts | 26 +++++++++++++++++ 7 files changed, 115 insertions(+), 7 deletions(-) diff --git a/apps/cli-docs/src/content/docs/exit-codes.md b/apps/cli-docs/src/content/docs/exit-codes.md index 24aba7a58..bf65334a8 100644 --- a/apps/cli-docs/src/content/docs/exit-codes.md +++ b/apps/cli-docs/src/content/docs/exit-codes.md @@ -40,6 +40,7 @@ react to failure categories without parsing stderr. | 41 | Seer No Budget | Seer requires a paid plan | | 42 | AI Disabled | AI features disabled by organization admin | | 43 | Seer Out Of Credits | Organization is out of Seer credits | +| 44 | Seer Setup Required | Seer needs a GitHub or GitLab integration, or repositories connected to the project | | 50 | Upgrade Error | CLI upgrade operation failed | | 51 | Device Flow Error | OAuth device authorization flow failed | | 60 | Output Error | Command produced output but the operation failed | diff --git a/packages/cli/AGENTS.md b/packages/cli/AGENTS.md index 8dea54d29..4836af154 100644 --- a/packages/cli/AGENTS.md +++ b/packages/cli/AGENTS.md @@ -907,7 +907,7 @@ CliError (base, exitCode=1) ├── ResolutionError (exitCode=23 — value provided but not found) ├── ValidationError (exitCode=21 — input validation) ├── DeviceFlowError (exitCode=51 — OAuth flow) -├── SeerError (exitCode=40–43 by reason — 'not_enabled' | 'no_budget' | 'ai_disabled' | 'out_of_credits') +├── SeerError (exitCode=40–44 by reason — 'not_enabled' | 'no_budget' | 'ai_disabled' | 'out_of_credits' | 'scm_integration_required' | 'repos_not_linked') ├── TimeoutError (exitCode=31 — operation timed out) └── UpgradeError (exitCode=50 — upgrade failures) ``` diff --git a/packages/cli/src/lib/errors.ts b/packages/cli/src/lib/errors.ts index ee4fe2b6f..1423027e3 100644 --- a/packages/cli/src/lib/errors.ts +++ b/packages/cli/src/lib/errors.ts @@ -29,6 +29,7 @@ import { buildBillingUrl, buildOrgSettingsUrl, buildSeerSettingsUrl, + buildSourceCodeIntegrationsUrl, } from "./sentry-urls.js"; import { getLoginCommand, getUrlLoginCommand, sentry } from "./app-identity.js"; @@ -85,6 +86,8 @@ export const EXIT = { SEER_AI_DISABLED: 42, /** Organization is out of Seer credits */ SEER_OUT_OF_CREDITS: 43, + /** Seer needs a GitHub/GitLab integration or repositories connected to the project */ + SEER_SETUP_REQUIRED: 44, // 50–59: Operations /** CLI upgrade failed */ @@ -765,7 +768,9 @@ export type SeerErrorReason = | "not_enabled" | "no_budget" | "ai_disabled" - | "out_of_credits"; + | "out_of_credits" + | "scm_integration_required" + | "repos_not_linked"; /** * Seer-specific errors with actionable suggestions. @@ -783,12 +788,17 @@ export class SeerError extends CliError { no_budget: "Seer requires a paid plan.", ai_disabled: "AI features are disabled for this organization.", out_of_credits: "Your organization is out of Seer credits.", + scm_integration_required: + "Seer needs a GitHub or GitLab integration for this organization.", + repos_not_linked: "Seer needs repositories connected to this project.", }; const exitCodes: Record = { not_enabled: EXIT.SEER_NOT_ENABLED, no_budget: EXIT.SEER_NO_BUDGET, ai_disabled: EXIT.SEER_AI_DISABLED, out_of_credits: EXIT.SEER_OUT_OF_CREDITS, + scm_integration_required: EXIT.SEER_SETUP_REQUIRED, + repos_not_linked: EXIT.SEER_SETUP_REQUIRED, }; super(messages[reason], exitCodes[reason]); this.name = "SeerError"; @@ -817,6 +827,9 @@ export class SeerError extends CliError { // Sentry doesn't say whether auto-reload is off or has run through its // top-ups, so the advice covers both. out_of_credits: `Someone with billing access can adjust Seer spend and auto-reload in Sentry:\n ${buildBillingUrl(this.orgSlug, "seer")}`, + // Setup gaps are fixed in Sentry settings, not with a trial. + scm_integration_required: `Connect GitHub or GitLab to Sentry:\n ${buildSourceCodeIntegrationsUrl(this.orgSlug)}`, + repos_not_linked: `Connect the project's repositories in Seer settings:\n ${buildSeerSettingsUrl(this.orgSlug)}`, }; return `${this.message}\n\n${suggestions[this.reason]}`; } @@ -831,6 +844,10 @@ export class SeerError extends CliError { // out_of_credits orgs already have Seer, so a trial is not the fix out_of_credits: "Someone with billing access can adjust Seer spend and auto-reload in your organization's billing settings.", + scm_integration_required: + "Connect GitHub or GitLab in your organization's integration settings.", + repos_not_linked: + "Connect the project's repositories in your organization's Seer settings.", }; return `${this.message}\n\n${fallbackSuggestions[this.reason]}`; } diff --git a/packages/cli/src/lib/formatters/seer.ts b/packages/cli/src/lib/formatters/seer.ts index d7cd1a3c8..fd771e607 100644 --- a/packages/cli/src/lib/formatters/seer.ts +++ b/packages/cli/src/lib/formatters/seer.ts @@ -11,7 +11,7 @@ import type { RootCause, SolutionArtifact, } from "../../types/seer.js"; -import { ApiError, SeerError } from "../errors.js"; +import { ApiError, SeerError, type SeerErrorReason } from "../errors.js"; import { cyan } from "./colors.js"; import { filterFields } from "./json.js"; import { renderMarkdown } from "./markdown.js"; @@ -272,6 +272,15 @@ export function jsonTransformIssueExplain( */ const OUT_OF_CREDITS_CODE = "seer_out_of_credits"; +/** + * `code`s Sentry sends with a 409 when it refuses to start a run because the + * org has no GitHub/GitLab integration or the project has no linked repos. + */ +const SETUP_REQUIRED_REASONS: readonly SeerErrorReason[] = [ + "scm_integration_required", + "repos_not_linked", +]; + /** * Create a SeerError from an API error status code and detail. * @@ -304,15 +313,20 @@ export function createSeerError( // (could be permission denied, rate limiting, etc.) return null; } + if (status === 409) { + const reason = SETUP_REQUIRED_REASONS.find((r) => r === code); + // Any other 409 (e.g. a run already in flight) keeps its API detail. + return reason ? new SeerError(reason, orgSlug) : null; + } return null; } /** * Convert an API error to a Seer-specific error or an ApiError. * - * Returns a SeerError for known Seer issues (402/403 with specific detail), - * or preserves the error as an ApiError for other failures. Previously - * returned a plain Error which lost the status code and endpoint — causing + * Returns a SeerError for known Seer issues (402/403 with specific detail, or + * a 409 setup code), or preserves the error as an ApiError for other failures. + * Previously returned a plain Error which lost the status code and endpoint — causing * CLI-N (84 users) where unrecognized 403s became untyped errors. * * @param status - HTTP status code diff --git a/packages/cli/test/commands/issue/explain.func.test.ts b/packages/cli/test/commands/issue/explain.func.test.ts index 5c3afb13c..1a56a37f2 100644 --- a/packages/cli/test/commands/issue/explain.func.test.ts +++ b/packages/cli/test/commands/issue/explain.func.test.ts @@ -20,7 +20,7 @@ vi.mock("../../../src/commands/issue/utils.js", async (importOriginal) => { import { explainCommand } from "../../../src/commands/issue/explain.js"; // oxlint-disable-next-line sentry-cli/no-namespace-import -- needed for spyOn mocking import * as issueUtils from "../../../src/commands/issue/utils.js"; -import { ApiError, ContextError } from "../../../src/lib/errors.js"; +import { ApiError, ContextError, EXIT } from "../../../src/lib/errors.js"; import type { IssueArgument } from "../../../src/lib/issue-arguments.js"; import type { AutofixState, RootCause } from "../../../src/types/seer.js"; @@ -190,6 +190,28 @@ describe("issue explain multiple IDs", () => { ).rejects.toMatchObject({ reason: "out_of_credits", orgSlug: "test-org" }); }); + test("throws repos_not_linked when Sentry refuses the analysis with a 409", async () => { + resolveSpy.mockResolvedValue({ org: "test-org", issueId: "1" }); + analyzeSpy.mockRejectedValue( + new ApiError( + "Failed to start root cause analysis: 409 Conflict", + 409, + "Seer Autofix requires repositories to be connected to this project before a new run can be started.", + undefined, + { code: "repos_not_linked" }, + ), + ); + + const func = await explainCommand.loader(); + await expect( + func.call(createMockContext().context, EXPLAIN_FLAGS, "IOS-1"), + ).rejects.toMatchObject({ + reason: "repos_not_linked", + orgSlug: "test-org", + exitCode: EXIT.SEER_SETUP_REQUIRED, + }); + }); + test("fails to parse when no issue ID is provided", async () => { const func = await explainCommand.loader(); await expect( diff --git a/packages/cli/test/lib/errors.test.ts b/packages/cli/test/lib/errors.test.ts index bbba9f3f2..3287e4400 100644 --- a/packages/cli/test/lib/errors.test.ts +++ b/packages/cli/test/lib/errors.test.ts @@ -405,6 +405,28 @@ describe("SeerError", () => { ); }); + test("format() links source code integrations for scm_integration_required", () => { + const err = new SeerError("scm_integration_required", "my-org"); + const formatted = err.format(); + expect(formatted).toContain( + "Seer needs a GitHub or GitLab integration for this organization.", + ); + expect(formatted).toContain( + "https://my-org.sentry.io/settings/integrations/?category=source%20code%20management", + ); + expect(formatted).not.toContain("trial"); + }); + + test("format() links Seer settings for repos_not_linked", () => { + const err = new SeerError("repos_not_linked", "my-org"); + const formatted = err.format(); + expect(formatted).toContain( + "Seer needs repositories connected to this project.", + ); + expect(formatted).toContain("https://my-org.sentry.io/settings/seer/"); + expect(formatted).not.toContain("trial"); + }); + test("format() shows fallback message without orgSlug", () => { const err = new SeerError("not_enabled"); const formatted = err.format(); @@ -789,6 +811,12 @@ describe("exit codes", () => { expect(new SeerError("out_of_credits").exitCode).toBe( EXIT.SEER_OUT_OF_CREDITS, ); + expect(new SeerError("scm_integration_required").exitCode).toBe( + EXIT.SEER_SETUP_REQUIRED, + ); + expect(new SeerError("repos_not_linked").exitCode).toBe( + EXIT.SEER_SETUP_REQUIRED, + ); }); test("TimeoutError has exit code TIMEOUT", () => { diff --git a/packages/cli/test/lib/formatters/seer.test.ts b/packages/cli/test/lib/formatters/seer.test.ts index 99cc4803b..1bb8875ed 100644 --- a/packages/cli/test/lib/formatters/seer.test.ts +++ b/packages/cli/test/lib/formatters/seer.test.ts @@ -385,6 +385,32 @@ describe("createSeerError", () => { expect(error?.orgSlug).toBe("my-org"); }); + test.each(["scm_integration_required", "repos_not_linked"] as const)( + "returns %s for a 409 with that setup code", + (code) => { + const error = createSeerError( + 409, + "Seer Autofix requires …", + "my-org", + code, + ); + expect(error).toBeInstanceOf(SeerError); + expect(error?.reason).toBe(code); + expect(error?.orgSlug).toBe("my-org"); + }, + ); + + test("returns null for a 409 without a setup code", () => { + expect( + createSeerError( + 409, + "A step is already running for this autofix run", + "my-org", + "run_in_flight", + ), + ).toBeNull(); + }); + test("returns out_of_credits for a 402 with the out-of-credits code", () => { const error = createSeerError( 402,