Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 10 additions & 72 deletions AGENTS.md

Large diffs are not rendered by default.

3 changes: 3 additions & 0 deletions src/commands/dashboard/create.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ import {
toNumericId,
} from "../../lib/resolve-target.js";
import { buildDashboardUrl } from "../../lib/sentry-urls.js";
import { setOrgProjectContext } from "../../lib/telemetry.js";
import type { DashboardDetail } from "../../types/dashboard.js";

type CreateFlags = {
Expand Down Expand Up @@ -85,13 +86,15 @@ async function resolveDashboardTarget(
): Promise<ResolvedDashboardTarget> {
switch (parsed.type) {
case "explicit": {
setOrgProjectContext([parsed.org], [parsed.project]);
const pid = await fetchProjectId(parsed.org, parsed.project);
return {
orgSlug: parsed.org,
projectIds: pid !== undefined ? [pid] : [],
};
}
case "org-all":
setOrgProjectContext([parsed.org], []);
return { orgSlug: parsed.org, projectIds: [] };

case "project-search": {
Expand Down
3 changes: 3 additions & 0 deletions src/commands/dashboard/resolve.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { listDashboards } from "../../lib/api-client.js";
import type { parseOrgProjectArg } from "../../lib/arg-parsing.js";
import { ContextError, ValidationError } from "../../lib/errors.js";
import { resolveOrg } from "../../lib/resolve-target.js";
import { setOrgProjectContext } from "../../lib/telemetry.js";
import { isAllDigits } from "../../lib/utils.js";
import {
type DashboardWidget,
Expand Down Expand Up @@ -52,9 +53,11 @@ export async function resolveOrgFromTarget(
switch (parsed.type) {
case "explicit":
case "org-all":
setOrgProjectContext([parsed.org], []);
return parsed.org;
case "project-search":
case "auto-detect": {
// resolveOrg already sets telemetry context
const resolved = await resolveOrg({ cwd });
if (!resolved) {
throw new ContextError("Organization", usageHint);
Expand Down
5 changes: 1 addition & 4 deletions src/commands/issue/explain.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,7 +74,7 @@ export const explainCommand = buildCommand({
},
async *func(this: SentryContext, flags: ExplainFlags, issueArg: string) {
applyFreshFlag(flags);
const { cwd, setContext } = this;
const { cwd } = this;

// Declare org outside try block so it's accessible in catch for error messages
let resolvedOrg: string | undefined;
Expand All @@ -88,9 +88,6 @@ export const explainCommand = buildCommand({
});
resolvedOrg = org;

// Set telemetry context so SeerError events carry the org tag
setContext([org], []);

// Ensure root cause analysis exists (triggers if needed)
const state = await ensureRootCauseAnalysis({
org,
Expand Down
23 changes: 10 additions & 13 deletions src/commands/issue/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@ import {
toNumericId,
} from "../../lib/resolve-target.js";
import { getApiBaseUrl } from "../../lib/sentry-client.js";
import { setOrgProjectContext } from "../../lib/telemetry.js";
import type {
ProjectAliasEntry,
SentryIssue,
Expand Down Expand Up @@ -349,6 +350,7 @@ async function resolveTargetsFromParsedArg(

case "explicit": {
// Single explicit target — fetch project ID for API query param
// Telemetry context is set by dispatchOrgScopedList before this handler runs.
const projectId = await fetchProjectId(parsed.org, parsed.project);
return {
targets: [
Expand All @@ -365,6 +367,7 @@ async function resolveTargetsFromParsedArg(

case "org-all": {
// List all projects in the specified org
// Telemetry context is set by dispatchOrgScopedList before this handler runs.
const projects = await listProjects(parsed.org);
const targets: ResolvedTarget[] = projects.map((p) => ({
org: parsed.org,
Expand Down Expand Up @@ -460,6 +463,10 @@ async function resolveTargetsFromParsedArg(
projectDisplay: m.name,
}));

const uniqueOrgs = [...new Set(targets.map((t) => t.org))];
const uniqueProjects = [...new Set(targets.map((t) => t.project))];
setOrgProjectContext(uniqueOrgs, uniqueProjects);

return {
targets,
footer:
Expand Down Expand Up @@ -819,7 +826,6 @@ async function fetchOrgAllIssues(
type OrgAllIssuesOptions = {
org: string;
flags: ListFlags;
setContext: (orgs: string[], projects: string[]) => void;
};

/**
Expand All @@ -832,7 +838,7 @@ type OrgAllIssuesOptions = {
async function handleOrgAllIssues(
options: OrgAllIssuesOptions
): Promise<IssueListResult> {
const { org, flags, setContext } = options;
const { org, flags } = options;
// Encode sort + query in context key so cursors from different searches don't collide.
const contextKey = buildPaginationContextKey("org", org, {
sort: flags.sort,
Expand All @@ -841,8 +847,6 @@ async function handleOrgAllIssues(
});
const cursor = resolveOrgCursor(flags.cursor, PAGINATION_KEY, contextKey);

setContext([org], []);

let issuesResult: IssuesPage;
try {
issuesResult = await withProgress(
Expand Down Expand Up @@ -915,7 +919,6 @@ type ResolvedTargetsOptions = {
parsed: ReturnType<typeof parseOrgProjectArg>;
flags: ListFlags;
cwd: string;
setContext: (orgs: string[], projects: string[]) => void;
};

/** Default --period value (used to detect user-implicit vs explicit). */
Expand Down Expand Up @@ -1049,15 +1052,11 @@ function build403Detail(originalDetail: string | undefined): string {
async function handleResolvedTargets(
options: ResolvedTargetsOptions
): Promise<IssueListResult> {
const { parsed, flags, cwd, setContext } = options;
const { parsed, flags, cwd } = options;

const { targets, footer, skippedSelfHosted, detectedDsns } =
await resolveTargetsFromParsedArg(parsed, cwd);

const orgs = [...new Set(targets.map((t) => t.org))];
const projects = [...new Set(targets.map((t) => t.project))];
setContext(orgs, projects);

if (targets.length === 0) {
if (skippedSelfHosted) {
throw new ContextError("Organization and project", USAGE_HINT, [
Expand Down Expand Up @@ -1479,7 +1478,7 @@ export const listCommand = buildListCommand("issue", {
},
},
async *func(this: SentryContext, flags: ListFlags, target?: string) {
const { cwd, setContext } = this;
const { cwd } = this;

const parsed = parseOrgProjectArg(target);

Expand All @@ -1502,7 +1501,6 @@ export const listCommand = buildListCommand("issue", {
handleResolvedTargets({
...ctx,
flags,
setContext,
});

const result = (await dispatchOrgScopedList({
Expand All @@ -1521,7 +1519,6 @@ export const listCommand = buildListCommand("issue", {
handleOrgAllIssues({
org: ctx.parsed.org,
flags,
setContext,
}),
},
})) as IssueListResult;
Expand Down
5 changes: 1 addition & 4 deletions src/commands/issue/plan.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ export const planCommand = buildCommand({
},
async *func(this: SentryContext, flags: PlanFlags, issueArg: string) {
applyFreshFlag(flags);
const { cwd, setContext } = this;
const { cwd } = this;

// Declare org outside try block so it's accessible in catch for error messages
let resolvedOrg: string | undefined;
Expand All @@ -207,9 +207,6 @@ export const planCommand = buildCommand({
});
resolvedOrg = org;

// Set telemetry context so SeerError events carry the org tag
setContext([org], []);

// Ensure root cause analysis exists (runs explain if needed)
const state = await ensureRootCauseAnalysis({
org,
Expand Down
39 changes: 32 additions & 7 deletions src/commands/issue/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ import {
} from "../../lib/resolve-target.js";
import { parseSentryUrl } from "../../lib/sentry-url-parser.js";
import { buildIssueUrl } from "../../lib/sentry-urls.js";
import { setOrgProjectContext } from "../../lib/telemetry.js";
import { isAllDigits } from "../../lib/utils.js";
import type { SentryIssue } from "../../types/index.js";
import { type AutofixState, isTerminalStatus } from "../../types/seer.js";
Expand Down Expand Up @@ -530,24 +531,28 @@ export async function resolveIssue(
const parsed = parseIssueArg(issueArg);
const commandHint = buildCommandHint(command, issueArg);

let result: ResolvedIssueResult;

switch (parsed.type) {
case "numeric":
return resolveNumericIssue(parsed.id, cwd, command);
result = await resolveNumericIssue(parsed.id, cwd, command);
break;

case "explicit": {
// Full context: org + project + suffix
const org = await resolveEffectiveOrg(parsed.org);
const fullShortId = expandToFullShortId(parsed.suffix, parsed.project);
const issue = await getIssueByShortId(org, fullShortId);
return { org, issue };
result = { org, issue };
break;
}

case "explicit-org-numeric": {
// Org + numeric ID — use org-scoped endpoint for proper region routing.
const org = await resolveEffectiveOrg(parsed.org);
try {
const issue = await getIssueInOrg(org, parsed.numericId);
return { org, issue };
result = { org, issue };
} catch (err) {
if (err instanceof ApiError && err.status === 404) {
throw new ResolutionError(
Expand All @@ -562,30 +567,40 @@ export async function resolveIssue(
}
throw err;
}
break;
}

case "explicit-org-suffix": {
// Org + suffix only - ambiguous without project, always errors
const org = await resolveEffectiveOrg(parsed.org);
return resolveExplicitOrgSuffix(org, parsed.suffix, commandHint);
result = await resolveExplicitOrgSuffix(org, parsed.suffix, commandHint);
break;
}

case "project-search":
// Project slug + suffix - search across orgs
return resolveProjectSearch(
result = await resolveProjectSearch(
parsed.projectSlug,
parsed.suffix,
cwd,
commandHint
);
break;

case "suffix-only":
// Just suffix - need DSN for org and project
return resolveSuffixOnly(parsed.suffix, cwd, commandHint);
result = await resolveSuffixOnly(parsed.suffix, cwd, commandHint);
break;

case "selector":
// Magic @ selector - fetch top issue by sort criteria
return resolveSelector(parsed.selector, parsed.org, cwd, commandHint);
result = await resolveSelector(
parsed.selector,
parsed.org,
cwd,
commandHint
);
break;

default: {
// Exhaustive check - this should never be reached
Expand All @@ -595,6 +610,16 @@ export async function resolveIssue(
);
}
}

// Set telemetry context from the resolved result
if (result.org) {
setOrgProjectContext(
[result.org],
result.issue.project?.slug ? [result.issue.project.slug] : []
);
}

return result;
}

/**
Expand Down
8 changes: 1 addition & 7 deletions src/commands/issue/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,7 @@ export const viewCommand = buildCommand({
},
async *func(this: SentryContext, flags: ViewFlags, issueArg: string) {
applyFreshFlag(flags);
const { cwd, setContext } = this;
const { cwd } = this;

// Resolve issue using shared resolution logic
const { org: orgSlug, issue } = await resolveIssue({
Expand All @@ -155,12 +155,6 @@ export const viewCommand = buildCommand({
command: "view",
});

// Set telemetry context
setContext(
orgSlug ? [orgSlug] : [],
issue.project?.slug ? [issue.project.slug] : []
);

if (flags.web) {
await openInBrowser(issue.permalink, "issue");
return;
Expand Down
6 changes: 1 addition & 5 deletions src/commands/log/list.ts
Original file line number Diff line number Diff line change
Expand Up @@ -645,7 +645,7 @@ export const listCommand = buildListCommand(
},
},
async *func(this: SentryContext, flags: ListFlags, ...args: string[]) {
const { cwd, setContext } = this;
const { cwd } = this;

const parsed = parseLogListArgs(args);

Expand All @@ -657,8 +657,6 @@ export const listCommand = buildListCommand(
cwd,
TRACE_USAGE_HINT
);
setContext([org], []);

if (flags.follow) {
// Banner (suppressed in JSON mode)
writeFollowBanner(
Expand Down Expand Up @@ -725,8 +723,6 @@ export const listCommand = buildListCommand(
cwd,
COMMAND_NAME
);
setContext([org], [project]);

if (flags.follow) {
writeFollowBanner(
flags.follow ?? DEFAULT_POLL_INTERVAL,
Expand Down
7 changes: 3 additions & 4 deletions src/commands/log/view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
resolveProjectBySlug,
} from "../../lib/resolve-target.js";
import { buildLogsUrl } from "../../lib/sentry-urls.js";
import { setOrgProjectContext } from "../../lib/telemetry.js";
import type { DetailedSentryLog } from "../../types/index.js";

const log = logger.withTag("log-view");
Expand Down Expand Up @@ -154,6 +155,7 @@ async function resolveTarget(
): Promise<ResolvedLogTarget | null> {
switch (parsed.type) {
case "explicit":
setOrgProjectContext([parsed.org], [parsed.project]);
return { org: parsed.org, project: parsed.project };

case "project-search": {
Expand Down Expand Up @@ -349,7 +351,7 @@ export const viewCommand = buildCommand({
},
async *func(this: SentryContext, flags: ViewFlags, ...args: string[]) {
applyFreshFlag(flags);
const { cwd, setContext } = this;
const { cwd } = this;
const cmdLog = logger.withTag("log.view");

// Parse positional args
Expand All @@ -365,9 +367,6 @@ export const viewCommand = buildCommand({
throw new ContextError("Organization and project", USAGE_HINT);
}

// Set telemetry context
setContext([target.org], [target.project]);

if (flags.web) {
await handleWebOpen(target.org, logIds);
return;
Expand Down
Loading
Loading