Skip to content

feat: Add explain and plan commands (Seer AI) #39

Merged
betegon merged 20 commits intomainfrom
feat/seer-commands
Jan 26, 2026
Merged

feat: Add explain and plan commands (Seer AI) #39
betegon merged 20 commits intomainfrom
feat/seer-commands

Conversation

@MathurAditya724
Copy link
Copy Markdown
Member

@MathurAditya724 MathurAditya724 commented Jan 22, 2026

closes #42

@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Jan 22, 2026

Codecov Results 📊

❌ Patch coverage is 78.60%. Project has 1775 uncovered lines.
❌ Project coverage is 62.56%. Comparing base (base) to head (head).

Files with missing lines (22)
File Patch % Lines
human.ts 18.35% ⚠️ 672 Missing
resolve-target.ts 8.11% ⚠️ 306 Missing
oauth.ts 21.71% ⚠️ 202 Missing
resolver.ts 3.23% ⚠️ 120 Missing
errors.ts 5.94% ⚠️ 95 Missing
api-client.ts 72.40% ⚠️ 77 Missing
fixture.ts 28.74% ⚠️ 62 Missing
api.ts 89.78% ⚠️ 47 Missing
seer.ts 75.54% ⚠️ 45 Missing
errors.ts 73.17% ⚠️ 33 Missing
seer.ts 77.04% ⚠️ 31 Missing
preload.ts 39.02% ⚠️ 25 Missing
detector.ts 87.79% ⚠️ 16 Missing
cache.ts 76.27% ⚠️ 14 Missing
telemetry.ts 89.87% ⚠️ 8 Missing
config.ts 97.11% ⚠️ 7 Missing
index.ts 95.06% ⚠️ 4 Missing
colors.ts 91.84% ⚠️ 4 Missing
env-file.ts 97.17% ⚠️ 3 Missing
alias.ts 98.56% ⚠️ 2 Missing
java.ts 97.22% ⚠️ 1 Missing
parser.ts 98.63% ⚠️ 1 Missing
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
- Coverage    63.80%    62.56%    -1.24%
==========================================
  Files           35        40        +5
  Lines         3956      4741      +785
  Branches         0         0         —
==========================================
+ Hits          2524      2966      +442
- Misses        1432      1775      +343
- Partials         0         0         —

Generated by Codecov Action

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds Seer (AI) integration to the CLI by introducing Autofix + Issue Summary types, API client methods, formatters, and new sentry issue explain / sentry issue fix commands, with accompanying Bun tests.

Changes:

  • Add Zod schemas + helpers for the Seer Autofix API and Issue Summary API responses.
  • Add CLI commands/utilities and formatters for showing root cause analysis, progress, and summaries.
  • Add Bun test coverage for new API client methods, formatters, and polling logic.

Reviewed changes

Copilot reviewed 16 out of 17 changed files in this pull request and generated 7 comments.

Show a summary per file
File Description
test/types/autofix.test.ts Adds unit tests for Autofix type helpers (terminal status, progress/root cause/PR URL extraction).
test/lib/formatters/summary.test.ts Adds tests for formatting Issue Summary output.
test/lib/formatters/autofix.test.ts Adds tests for Autofix output formatting, progress messaging, and error mapping.
test/lib/api-client.autofix.test.ts Adds mocked-fetch tests for Autofix + Issue Summary API client methods.
test/commands/issue/utils.test.ts Adds tests for issue ID/org resolution and Autofix polling behavior.
src/types/sentry.ts Introduces Zod schemas/types for Issue Summary responses.
src/types/index.ts Re-exports new Autofix and Issue Summary types/schemas via the public types barrel.
src/types/autofix.ts Adds Autofix Zod schemas/types plus helper extractors (root causes, progress, PR URL, solution).
src/lib/formatters/summary.ts Adds human-readable formatting for Issue Summary output.
src/lib/formatters/index.ts Re-exports new Autofix/Summary formatter modules.
src/lib/formatters/autofix.ts Adds Autofix-specific progress, root cause, status, error, and solution formatting.
src/lib/api-client.ts Adds API client methods for triggering/polling/updating Autofix and fetching Issue Summary.
src/commands/issue/utils.ts Adds shared issue utilities: org+issue resolution and Autofix polling with spinner.
src/commands/issue/index.ts Registers new issue explain and issue fix subcommands and updates command docs.
src/commands/issue/fix.ts Implements sentry issue fix command logic (validate analysis, choose cause, continue run, render solution).
src/commands/issue/explain.ts Implements sentry issue explain command logic (trigger/poll analysis, render causes).
bun.lock Updates dependencies lockfile (notably @types/node version).

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +201 to +202
// Update autofix to continue to PR creation
await updateAutofix(org, numericId, state.run_id);
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The selected root cause (causeId / selectedCause) is validated and shown to the user, but it is never sent to the API. As a result, --cause has no effect and the backend can’t know which root cause to proceed with. Update the autofix update call to include the chosen cause (e.g., send cause_id / selection payload alongside run_id) and thread that through updateAutofix’s API client signature.

Suggested change
// Update autofix to continue to PR creation
await updateAutofix(org, numericId, state.run_id);
// Update autofix to continue to PR creation, including the selected cause
await updateAutofix(org, numericId, state.run_id, causeId);

Copilot uses AI. Check for mistakes.
Comment on lines +445 to +453
export async function getAutofixState(
orgSlug: string,
issueId: string
): Promise<AutofixState | null> {
const response = await apiRequest<AutofixResponse>(
`/organizations/${orgSlug}/issues/${issueId}/autofix/`
);

return response.autofix;
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getAutofixState doesn’t validate the response payload. Since you already have AutofixResponseSchema, pass it as schema to apiRequest so malformed/changed API responses are caught early instead of propagating unknown shapes through the CLI.

Copilot uses AI. Check for mistakes.
Comment on lines +462 to +475
* @param payload - The update payload (select_root_cause, select_solution, create_pr)
* @returns The response from the API
*/
export function updateAutofix(
orgSlug: string,
issueId: string,
runId: number
): Promise<unknown> {
return apiRequest(`/organizations/${orgSlug}/issues/${issueId}/autofix/`, {
method: "POST",
body: {
run_id: runId,
step: "solution",
},
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The JSDoc for updateAutofix documents a payload parameter, but the function signature and implementation don’t accept or use it. Either add the payload parameter and forward it to the API request body, or update the docs to match the actual API contract.

Suggested change
* @param payload - The update payload (select_root_cause, select_solution, create_pr)
* @returns The response from the API
*/
export function updateAutofix(
orgSlug: string,
issueId: string,
runId: number
): Promise<unknown> {
return apiRequest(`/organizations/${orgSlug}/issues/${issueId}/autofix/`, {
method: "POST",
body: {
run_id: runId,
step: "solution",
},
* @param payload - The update payload (select_root_cause, select_solution, create_pr). If omitted, defaults to { step: "solution" }.
* @returns The response from the API
*/
export function updateAutofix(
orgSlug: string,
issueId: string,
runId: number,
payload?: Record<string, unknown>
): Promise<unknown> {
const body = payload
? {
run_id: runId,
...payload,
}
: {
run_id: runId,
step: "solution",
};
return apiRequest(`/organizations/${orgSlug}/issues/${issueId}/autofix/`, {
method: "POST",
body,

Copilot uses AI. Check for mistakes.
Comment on lines +465 to +476
export function updateAutofix(
orgSlug: string,
issueId: string,
runId: number
): Promise<unknown> {
return apiRequest(`/organizations/${orgSlug}/issues/${issueId}/autofix/`, {
method: "POST",
body: {
run_id: runId,
step: "solution",
},
});
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

updateAutofix currently hardcodes { step: "solution" } and returns Promise<unknown>, which makes it easy to accidentally ship an incorrect request shape (and it can’t support selecting a root cause or creating a PR). Consider accepting a typed AutofixUpdatePayload (and/or explicit step/stopping point) and validating the response with a Zod schema, similar to triggerAutofix.

Suggested change
export function updateAutofix(
orgSlug: string,
issueId: string,
runId: number
): Promise<unknown> {
return apiRequest(`/organizations/${orgSlug}/issues/${issueId}/autofix/`, {
method: "POST",
body: {
run_id: runId,
step: "solution",
},
});
const AutofixUpdateRequestSchema = z.object({
run_id: z.number(),
step: z.enum(["select_root_cause", "select_solution", "create_pr"]).optional(),
root_cause_id: z.string().optional(),
solution_id: z.string().optional(),
});
type AutofixUpdateRequest = z.infer<typeof AutofixUpdateRequestSchema>;
export type AutofixUpdatePayload = Omit<AutofixUpdateRequest, "run_id">;
export function updateAutofix(
orgSlug: string,
issueId: string,
runId: number,
payload: AutofixUpdatePayload = { step: "select_solution" }
): Promise<AutofixTriggerResponse> {
const requestBody = AutofixUpdateRequestSchema.parse({
run_id: runId,
...payload,
});
return apiRequest<AutofixTriggerResponse>(
`/organizations/${orgSlug}/issues/${issueId}/autofix/`,
{
method: "POST",
body: requestBody,
schema: AutofixTriggerResponseSchema,
}
);

Copilot uses AI. Check for mistakes.
let currentMessage = "Waiting for analysis to start...";

// Animation timer runs independently of polling for smooth spinner
let animationTimer: Timer | undefined;
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Timer isn’t declared in this module and can be environment/type-package dependent. Prefer ReturnType<typeof setInterval> (or NodeJS.Timeout if you want Node-specific) so the interval handle type stays correct across Bun/Node without relying on a global Timer name.

Suggested change
let animationTimer: Timer | undefined;
let animationTimer: ReturnType<typeof setInterval> | undefined;

Copilot uses AI. Check for mistakes.
Comment on lines +49 to +54
if (summary.scores?.possibleCauseConfidence !== null) {
const confidence = summary.scores?.possibleCauseConfidence;
if (confidence !== undefined) {
const percent = Math.round(confidence * 100);
lines.push(muted(`Confidence: ${percent}%`));
}
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The confidence check is harder to read than necessary and relies on a !== null guard plus a second undefined check. Consider simplifying to a single typeof confidence === "number" (or confidence != null) to make the intent clearer and avoid treating undefined as a meaningful value.

Suggested change
if (summary.scores?.possibleCauseConfidence !== null) {
const confidence = summary.scores?.possibleCauseConfidence;
if (confidence !== undefined) {
const percent = Math.round(confidence * 100);
lines.push(muted(`Confidence: ${percent}%`));
}
const confidence = summary.scores?.possibleCauseConfidence;
if (typeof confidence === "number") {
const percent = Math.round(confidence * 100);
lines.push(muted(`Confidence: ${percent}%`));

Copilot uses AI. Check for mistakes.
Comment on lines +325 to +330
if (!state.steps) {
return;
}

// Look for PR info in steps or coding_agents
for (const step of state.steps) {
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

extractPrUrl returns early when state.steps is undefined, but PR information can still exist under state.coding_agents (as your own logic later supports). This early return prevents extracting the PR URL in that case. Consider removing the early return and instead treating missing steps as an empty list so coding_agents can still be checked.

Suggested change
if (!state.steps) {
return;
}
// Look for PR info in steps or coding_agents
for (const step of state.steps) {
// Look for PR info in steps or coding_agents
const steps = state.steps ?? [];
for (const step of steps) {

Copilot uses AI. Check for mistakes.
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 13 out of 14 changed files in this pull request and generated 5 comments.


💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

}

// Trigger solution planning to continue to PR creation
await triggerSolutionPlanning(org, numericId, state.run_id);
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

--cause/causeId is validated and displayed, but it is never sent to the API. As a result, selecting a root cause has no effect on what the backend plans. Add an API call/payload to select the root cause (and any stopping point) before triggering solution planning, or include the selected cause in the request if the endpoint supports it.

Suggested change
await triggerSolutionPlanning(org, numericId, state.run_id);
await triggerSolutionPlanning(org, numericId, state.run_id, { causeId });

Copilot uses AI. Check for mistakes.
Comment on lines +125 to +131
brief: "Create a PR with a plan using Seer AI",
fullDescription:
"Create a pull request with a plan for a Sentry issue using Seer AI.\n\n" +
"This command requires that 'sentry issue explain' has been run first " +
"to identify the root cause. It will then generate code changes and " +
"create a pull request with the plan.\n\n" +
"If multiple root causes were identified, use --cause to specify which one.\n\n" +
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The command docs claim it will "generate code changes and create a pull request", but the implementation only triggers the "solution" step and outputs the solution artifact (no PR URL/number is surfaced). Either adjust the documentation to match current behavior (generate a plan) or implement/trigger the PR creation step and print the resulting PR info.

Copilot uses AI. Check for mistakes.
Comment on lines +418 to +447
export function triggerRootCauseAnalysis(
orgSlug: string,
issueId: string
): Promise<{ run_id: number }> {
return apiRequest<{ run_id: number }>(
`/organizations/${orgSlug}/issues/${issueId}/autofix/`,
{
method: "POST",
body: { step: "root_cause" },
}
);
}

/**
* Get the current autofix state for an issue.
*
* @param orgSlug - The organization slug
* @param issueId - The numeric Sentry issue ID
* @returns The autofix state, or null if no autofix has been run
*/
export async function getAutofixState(
orgSlug: string,
issueId: string
): Promise<AutofixState | null> {
const response = await apiRequest<AutofixResponse>(
`/organizations/${orgSlug}/issues/${issueId}/autofix/`
);

return response.autofix;
}
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These new Autofix API helpers are not passing a Zod schema to apiRequest, unlike the existing high-level API methods in this file. Consider importing AutofixResponseSchema (and a small schema for the trigger response) and providing schema: so malformed responses fail fast and types stay trustworthy.

Copilot uses AI. Check for mistakes.
const lines: string[] = [];

// Cause header
lines.push(`${yellow(`Cause #${index}`)}: ${cause.description}`);
Copy link

Copilot AI Jan 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

formatRootCause labels causes as Cause #${index} even though each RootCause already has an id field. If the API returns non-sequential IDs or the list is filtered/reordered, the displayed ID won’t match the actual cause ID users should reference. Prefer displaying cause.id (and keep --cause selection consistent with that).

Suggested change
lines.push(`${yellow(`Cause #${index}`)}: ${cause.description}`);
const displayId = cause.id ?? index;
lines.push(`${yellow(`Cause #${displayId}`)}: ${cause.description}`);

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Jan 23, 2026

Semver Impact of This PR

🟡 Minor (new features)

📋 Changelog Preview

This is how your changes will appear in the changelog.
Entries from this PR are highlighted with a left border (blockquote style).


New Features ✨

Issue

  • Add workspace-scoped alias cache by BYK in #52
  • Add short ID aliases for multi-project support by BYK in #31

Other

  • (api) Align with gh api and curl conventions by BYK in #60
  • (auth) Add press 'c' to copy URL during login flow by betegon in #58
  • (commands) Rename get commands to view and add -w browser flag by BYK in #53
  • Add explain and plan commands (Seer AI) by MathurAditya724 in #39
  • Add Sentry SDK for error tracking and usage telemetry by BYK in #63

Bug Fixes 🐛

Issue

  • Handle cross-org project slug collisions in alias generation by BYK in #62
  • Use org-scoped endpoint for latest event + enhanced display by betegon in #40

Other

  • (api) Use query params for --field with GET requests by BYK in #59

Documentation 📚

  • (readme) Add installation section by betegon in #65
  • Update command references from 'get' to 'view' and document -w flag by BYK in #54

Internal Changes 🔧

  • (release) Fix changelog-preview permissions by BYK in #41
  • Rename config folder from .sentry-cli-next to .sentry by BYK in #50

🤖 This preview updates automatically when you update the PR.

@betegon betegon changed the title feat: implmented the seer api feat: Add explain and plan commands (Seer AI) Jan 23, 2026
Copy link
Copy Markdown
Member

@betegon betegon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review Comments

Thanks for adding the Seer AI integration! The implementation looks solid overall. I have a few suggestions to improve consistency and maintainability:


1. Rename autofixseer (naming consistency)

Files: src/types/autofix.ts, src/lib/formatters/autofix.ts, and corresponding test files

The term "autofix" is Sentry's internal API naming, but users interact with sentry issue explain and sentry issue plan — they never see "autofix" anywhere.

Please rename these files to use seer (the product name):

  • src/types/autofix.tssrc/types/seer.ts
  • src/lib/formatters/autofix.tssrc/lib/formatters/seer.ts
  • test/types/autofix.test.tstest/types/seer.test.ts
  • test/lib/formatters/autofix.test.tstest/lib/formatters/seer.test.ts

This makes the codebase more intuitive — when someone looks for "explain" or "plan" related code, seer.ts is more discoverable than autofix.ts.


2. Extract polling to src/lib/polling.ts

File: src/commands/issue/utils.ts (lines 111-163)

The pollAutofixState() function is generic enough to be reused by other commands. Please extract it to src/lib/polling.ts as a general utility.

This would allow future commands to reuse the same polling pattern with spinner animation.


3. Stale comment reference to "fix command"

File: src/types/autofix.ts:165

// Solution Artifact (from fix command)

This should say "plan command" since the command was renamed from fix to plan.


4. JSDoc improvements needed

Several exported functions are missing @param and @returns documentation:

src/types/autofix.ts:

  • isTerminalStatus (line 268) — missing @param status, @returns
  • extractRootCauses (line 275) — missing @param state, @returns

src/lib/formatters/autofix.ts:

  • getSpinnerFrame (line 26) — missing @param tick, @returns
  • formatRootCauseHeader (line 161) — missing @returns

src/commands/issue/plan.ts:

  • validateCauseSelection (line 84) — has only brief description, missing @param, @returns, @throws

5. Consider --force flag for explain command

File: src/commands/issue/explain.ts

Currently, if an analysis already exists and is COMPLETED, the command returns cached results. It might make sense to add a --force flag that triggers a fresh analysis even when one exists.

This could be useful when:

  • The issue has new events since the last analysis
  • The user wants to retry after fixing code mappings

Example: sentry issue explain 123456 --force


Let me know if you have questions on any of these!

@betegon betegon self-requested a review January 26, 2026 13:12
Copy link
Copy Markdown
Member

@betegon betegon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

comments above

@betegon
Copy link
Copy Markdown
Member

betegon commented Jan 26, 2026

I'm also worried about the 10 minutes timeout on the pooling. isn't it too much? I mean for agents, imagine your claude code or codex waiting for 10mins (if they don't time out earlier).

wdyt of a 3mins timeout or something? then we can say, try it again and the agent will do it again. maybe we could ask the seer team on the average root cause analysis duration

@MathurAditya724
Copy link
Copy Markdown
Member Author

yeah 3 mins seems reasonable, will change that

@betegon
Copy link
Copy Markdown
Member

betegon commented Jan 26, 2026

one more @MathurAditya724 , we show this message when running explain:

Starting root cause analysis...

It'd be cool if we put something like:

Starting root cause analysis, it can take several minutes

The first time I run one it was instant as the issue had it already, and then run another one where it had to run and it's taking a bit, so that text could help on what to expect, both for users and agents.

@betegon betegon self-requested a review January 26, 2026 16:58
Copy link
Copy Markdown
Member

@betegon betegon left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving this so we can get it merged.

I'll take care of a follow up here to make explain and plan support short issue ids like the other commands do.

@betegon betegon merged commit a3dae64 into main Jan 26, 2026
14 checks passed
@betegon betegon deleted the feat/seer-commands branch January 26, 2026 17:54
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(issue): Add explain and plan commands (Seer AI)

3 participants