Skip to content

feat(alert): add alert list/view commands with pagination fixes#579

Open
betegon wants to merge 25 commits into
mainfrom
feat/alert-list-commands
Open

feat(alert): add alert list/view commands with pagination fixes#579
betegon wants to merge 25 commits into
mainfrom
feat/alert-list-commands

Conversation

@betegon
Copy link
Copy Markdown
Member

@betegon betegon commented Mar 26, 2026

Closes #578

Adds full sentry alert CRUD coverage for both issue alerts (project-scoped) and metric alerts (org-scoped), plus pagination correctness fixes and generated docs/CI follow-ups.

What this PR includes

Alert list commands

  • sentry alert issues list (project-scoped issue alert rules)
  • sentry alert metrics list (org-scoped metric alert rules)

Both support target resolution, pagination, --json, --web, and query filtering.

Alert view commands

  • sentry alert issues view <org/project/rule-id-or-name>
  • sentry alert metrics view <org/rule-id-or-name>

Both support ID-or-name lookup and --web.

Alert create commands

  • sentry alert issues create <org/project> with required --name, --condition, --action, --action-match, plus optional --frequency, --environment, --filter, --filter-match, --owner, and --dry-run.
  • sentry alert metrics create <org> with required --name, --query, --aggregate, --dataset, --time-window, --trigger, plus optional --project, --environment, --owner, and --dry-run.

Alert edit commands

  • sentry alert issues edit <org/project/rule-id-or-name> now supports merge-based updates beyond name/status, including conditions/actions/match modes/frequency/environment/filters/owner.
  • sentry alert metrics edit <org/rule-id-or-name> now supports merge-based updates beyond name/status, including query/aggregate/dataset/time-window/triggers/projects/environment/owner.

Both edit flows resolve by ID-or-name, fetch the current rule, merge only provided flags, validate the merged payload, and PUT the full document.

Alert delete commands

  • sentry alert issues delete <org/project/rule-id-or-name>
  • sentry alert metrics delete <org/rule-id-or-name>

Both use interactive confirmation by default with --yes / --force bypass and --dry-run preview.

API + resolver improvements

  • Added createIssueAlertRule and createMetricAlertRule API helpers and exports.
  • Shared isNotFoundApiError for alert rule resolution paths.
  • get*Rule and get*Document now share a single GET per resource type via shared fetch helpers.

Pagination correctness fixes

  • Fixes hasMore handling for exact-limit pages without a next cursor.
  • Preserves hasMore when client-side filtering yields an empty display page but upstream pages still exist.
  • Adds regression tests for both alert list commands.

Follow-up fixes from CI

  • Switched alert route import to telemetry-aware buildRouteMap wrapper.
  • Added missing docs/src/fragments/commands/alert.md fragment.
  • Regenerated skill/docs references for alert command docs.

Test plan

# create
sentry alert issues create my-org/my-project \
  --name "Error Spike" \
  --condition '{"id":"sentry.rules.conditions.first_seen_event.FirstSeenEventCondition"}' \
  --action '{"id":"sentry.mail.actions.NotifyEmailAction","targetType":"Team","targetIdentifier":1}' \
  --action-match any --dry-run

sentry alert metrics create my-org \
  --name "P95 Latency" \
  --query "environment:prod" \
  --aggregate "p95(transaction.duration)" \
  --dataset transactions \
  --time-window 5 \
  --trigger '{"alertThreshold":500,"actions":[{"id":"sentry.mail.actions.NotifyEmailAction","targetType":"Team","targetIdentifier":1}]}' \
  --dry-run

# list / view
sentry alert issues list my-org/my-project
sentry alert metrics list my-org/
sentry alert issues view my-org/my-project/12345
sentry alert metrics view my-org/67890

# edit
sentry alert issues edit my-org/my-project/12345 --name "Prod errors" --status disabled
sentry alert metrics edit my-org/67890 --query "environment:prod event.type:error" --time-window 15

# delete
sentry alert issues delete my-org/my-project/12345 --dry-run
sentry alert metrics delete my-org/67890 --yes

# tests
bun test test/commands/alert

# docs / checks
bun run generate:docs
bun run check:fragments
bun run lint

betegon added 2 commits March 26, 2026 20:51
Adds two new list commands under `sentry alert`:
- `sentry alert issues list` — list issue alert rules for one or more projects,
  with full multi-target resolution (DSN auto-detect, org-all, project-search,
  explicit org/project), compound cursor pagination, Phase 1 + Phase 2 budget
  redistribution, and graceful partial failure handling
- `sentry alert metrics list` — list metric alert rules for an org

Both commands support --json, --limit, --web, and -c next/prev pagination.
…ed lib

Moves duplicated code from issue/list.ts and alert/issues/list.ts into
shared modules so both commands use the same implementation:

- lib/db/pagination.ts: exports CURSOR_SEP, encodeCompoundCursor,
  decodeCompoundCursor, buildMultiTargetContextKey
- lib/resolve-target.ts: exports resolveTargetsFromParsedArg — handles all
  four target modes (auto-detect, explicit, org-all, project-search) with
  options for enrichProjectIds and checkIssueShortId (issue-list-specific)

issue/list.ts drops ~260 lines of local duplicates; alert/issues/list.ts
never needs to define them in the first place.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 26, 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 ✨

Dashboard

  • Add pagination and glob filtering to dashboard list by BYK in #560
  • Add a full chart rendering engine for sentry dashboard view that transforms widget data into rich terminal visualizations. by BYK in #555

Init

  • Surface server-provided detail in spinner messages by MathurAditya724 in #588
  • Propagate sentry-trace headers to wizard API calls by betegon in #567
  • Treat bare slug as new project name when not found by BYK in #554

Other

  • (alert) Add alert issues and metrics list commands by betegon in #579
  • (formatters) Colorize SQL in DB span descriptions by BYK in #546
  • (output) Add Zod schema registration to OutputConfig for self-documenting JSON fields by BYK in #582
  • (telemetry) Report unknown commands to Sentry by BYK in #563
  • Expose CLI as a programmatic library by BYK in #565
  • Bidirectional cursor pagination (-c next / -c prev) by BYK in #564
  • Add sentry sourcemap inject and sentry sourcemap upload commands by BYK in #547
  • Native debug ID injection and sourcemap upload by BYK in #543

Bug Fixes 🐛

Dashboard

  • Fix table widget rendering and timeseries bar chart width by BYK in #584
  • Validate display types against all datasets by betegon in #577
  • Auto-clamp widget limit instead of erroring by BYK in #573
  • Default issue dataset table columns to ["issue"] by betegon in #570
  • Scale timeseries bar width to fill chart area by BYK in #562
  • Resolve dashboard by ID/slug in addition to title by BYK in #559

Event

  • Detect SHORT-ID/EVENT-ID format in event view by BYK in #574
  • Auto-fallback to org-wide search when event 404s in project by BYK in #575

Other

  • (api) Show meaningful message for network errors instead of '0 Unknown' by BYK in #572
  • (auth) Skip stale cached user info for env var tokens in auth status by BYK in #589
  • (event-view) Auto-redirect issue short IDs in two-arg form (CLI-MP) by BYK in #558
  • (help) Show help when user passes help as positional arg by BYK in #561
  • (issue) Auto-redirect bare org slug to org-all mode in issue list by BYK in #576
  • (log) Use 30d default period and show newest logs first by sergical in #568
  • Reject @-selectors in parseOrgProjectArg with helpful redirect by BYK in #557

Documentation 📚

  • Add missing command pages for trace, span, sourcemap, repo, trial, schema by sergical in #569

Internal Changes 🔧

Coverage

  • Use informational-patch input instead of sed hack by BYK in #544
  • Make checks informational on release branches by BYK in #541

Event

  • Replace "latest" magic string with @latest sentinel constant by BYK in #583
  • Deduplicate span tree building into shared helper by BYK in #581

Other

  • (api) Collapse stats on issue detail endpoints to save 100-300ms by BYK in #551
  • (ci) Upgrade GitHub Actions to Node 24 runtime by BYK in #542
  • (db) DRY up database layer with shared helpers and lint enforcement by BYK in #550
  • (docs) Polish sidebar, header, focus, and code block UX by sergical in #580
  • (issue-list) Use collapse parameter to skip unused Snuba queries by BYK in #545
  • Regenerate skill files and command docs by github-actions[bot] in 0276f760
  • Regenerate skill files and command docs by github-actions[bot] in efa746d2
  • Bump Bun from 1.3.9 to 1.3.11 by BYK in #552
  • Regenerate skill files by github-actions[bot] in ec1ffe28

Other

  • release: 0.21.0 by betegon in 6c89fa24

🤖 This preview updates automatically when you update the PR.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 26, 2026

PR Preview Action v1.8.1

QR code for preview link

🚀 View preview at
https://cli.sentry.dev/_preview/pr-579/

Built to branch gh-pages at 2026-05-26 20:48 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Mar 26, 2026

Codecov Results 📊

❌ Patch coverage is 60.20%. Project has 4669 uncovered lines.
❌ Project coverage is 81.08%. Comparing base (base) to head (head).

Files with missing lines (21)
File Patch % Lines
src/commands/alert/issues/list.ts 57.36% ⚠️ 84 Missing and 28 partials
src/commands/alert/metrics/list.ts 57.29% ⚠️ 82 Missing and 26 partials
src/commands/alert/mutation-utils.ts 55.56% ⚠️ 28 Missing and 12 partials
src/lib/api/alerts.ts 22.22% ⚠️ 28 Missing and 2 partials
src/commands/alert/metrics/edit.ts 82.76% ⚠️ 10 Missing and 17 partials
src/commands/alert/issues/edit.ts 80.39% ⚠️ 10 Missing and 14 partials
src/commands/alert/metrics/delete.ts 7.69% ⚠️ 24 Missing
src/commands/alert/issues/delete.ts 8.00% ⚠️ 23 Missing
src/commands/alert/issues/rule-resolve.ts 78.57% ⚠️ 12 Missing and 11 partials
src/lib/resolve-target.ts 58.14% ⚠️ 18 Missing and 5 partials
src/commands/alert/issues/create.ts 66.67% ⚠️ 14 Missing and 8 partials
src/commands/alert/metrics/rule-resolve.ts 79.63% ⚠️ 11 Missing and 11 partials
src/commands/alert/metrics/create.ts 72.50% ⚠️ 11 Missing and 9 partials
src/lib/api/infrastructure.ts 0.00% ⚠️ 14 Missing
src/commands/alert/metrics/view.ts 50.00% ⚠️ 9 Missing and 1 partials
src/commands/alert/issues/view.ts 50.00% ⚠️ 8 Missing and 1 partials
src/lib/sentry-urls.ts 0.00% ⚠️ 6 Missing
src/lib/db/pagination.ts 94.12% ⚠️ 1 Missing and 1 partials
src/lib/org-list.ts 92.31% ⚠️ 1 Missing and 1 partials
src/commands/issue/list.ts 100.00% ⚠️ 1 partials
src/lib/alias.ts 100.00% ⚠️ 1 partials
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
- Coverage    81.96%    81.08%    -0.88%
==========================================
  Files          329       347       +18
  Lines        23749     24673      +924
  Branches     15502     16162      +660
==========================================
+ Hits         19463     20004      +541
- Misses        4286      4669      +383
- Partials      1643      1781      +138

Generated by Codecov Action

betegon and others added 11 commits March 26, 2026 21:30
…List

Both alert/issues/list and alert/metrics/list were implementing their own
4-mode target dispatch and compound cursor machinery from scratch, bypassing
the shared dispatchOrgScopedList infrastructure every other list command uses.

Replace with the standard pattern:
- dispatchOrgScopedList with ListCommandMeta + 4 mode handler overrides
- Simple parallel fetch-all for auto-detect/explicit/project-search modes
  (no compound cursor — alert rule lists are small datasets)
- Single-cursor pagination for org-all mode (metrics: listMetricAlertsPaginated
  with cursor; issues: resolveTargetsFromParsedArg for project list + fetch all)

Removes ~320 lines of custom dispatch and compound cursor logic.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ucture

- Use jsonTransformListResult (shared) instead of custom JSON transforms
- Separate raw rules into items (for JSON) and displayRows (for human output)
- Add --query/-q flag for client-side name filtering on both alert commands
- Restore api-client.ts alerts export to original shape

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ssue list

Full structural alignment with sentry issue list:
- FetchResult success/failure wrapping per target
- fetchWithBudget with two-phase surplus redistribution
- Compound cursor pagination (encodeCompoundCursor/decodeCompoundCursor)
- buildProjectAliasMap with buildOrgAwareAliases + setProjectAliases
- trimWithProjectGuarantee for per-project representation
- One handleResolvedTargets for all 4 modes (alert issues have no org-level API)
- allowCursorInModes for all modes including org-all
- parseCursorFlag explicitly in cursor flag definition
- logger.warn for partial failures
- IssueAlertListResult with title/footerMode/moreHint/footer
- jsonTransformListResult (shared JSON transform)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…issue list

Full structural alignment with sentry issue list and alert issues list:
- FetchResult success/failure wrapping per org with withAuthGuard
- fetchWithBudget with two-phase surplus redistribution (per-org)
- Compound cursor pagination across multiple orgs (auto-detect/explicit/project-search)
- trimWithOrgGuarantee for per-org representation
- handleResolvedOrgs for multi-org modes; handleOrgAllMetricAlerts for single-org cursor
- allowCursorInModes for auto-detect/explicit/project-search
- parseCursorFlag explicitly in cursor flag definition
- logger.warn for partial org failures
- MetricAlertListResult with title/footerMode/moreHint/footer
- jsonTransformListResult (shared JSON transform)
- buildMultiOrgContextKey isolates cursors per unique org set + query

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…list

Removes the separate handleOrgAllMetricAlerts handler and routes all 4
modes (including org-all) through handleResolvedOrgs. org-all resolves
all projects in the specified org, deduplicates to unique orgs (just the
one), then uses fetchWithBudget with compound cursor — same path as
auto-detect/explicit/project-search. This makes the structure identical
to alert issues list and supports multi-org results from DSN detection
consistently across all modes.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Extract FetchResult<T>, trimWithGroupGuarantee<T>, buildProjectAliasMap<T>,
and buildMultiOrgContextKey into lib/ so all three list commands (issue,
alert issues, alert metrics) share the same implementations. Each command
retains a thin domain-specific wrapper where needed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
For metric alerts (org-scoped), explicit and org-all modes already provide
the org slug in the parsed arg. Routing them through resolveTargetsFromParsedArg
caused an unnecessary listProjects API call just to re-derive the org slug.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Made-with: Cursor

# Conflicts:
#	docs/public/.well-known/skills/index.json
#	plugins/sentry-cli/skills/sentry-cli/SKILL.md
#	src/commands/issue/list.ts
#	src/lib/sentry-urls.ts
Correct hasMore computation in alert issue/metric list flows so exact-limit pages without a next cursor don't report false positives, and filtered-empty pages still retain next-page availability. Add regression tests for both commands and clean up a leftover issue list merge marker/imports that blocked command loading.

Made-with: Cursor
Add alert rule detail fetch endpoints and new `alert issues view` / `alert metrics view` commands with ID-or-name resolution across resolved targets, plus docs/skill reference updates for the new subcommands.

Made-with: Cursor
Switch alert route wiring to the telemetry-aware route-map wrapper and add the missing alert command fragment so generated docs validation passes in CI.

Made-with: Cursor
@MathurAditya724 MathurAditya724 changed the title feat(alert): add alert issues and metrics list commands feat(alert): add alert list/view commands with pagination fixes Apr 23, 2026
MathurAditya724 and others added 8 commits April 23, 2026 23:48
- View: fail fast on malformed org/project, suppress only 404 in numeric
  lookup, name match exact then suggestions (no auto fuzzy pick)
- List: preserve hasMore from phase-1 when phase-2 is skipped; drop dead
  footerMode; remove unused isMultiOrg
- Add view tests and phase1HasMore list regression test

Made-with: Cursor
- API: delete/put helpers, apiRequestToRegionNoContent for empty responses
- Shared rule-resolve modules for view/delete/edit (parse + resolve)
- issues|metrics delete: buildDeleteCommand, type org/project/id to confirm
- issues|metrics edit: --name and --status (active|disabled)
- Fix project-structure doc: dedupe subcommand label for nested routes (alert)
- Regenerate SKILL + references/alert only; other skill refs left at HEAD to
  avoid unrelated example-date churn from generate:skill

Made-with: Cursor
- Add isNotFoundApiError in lib/api/error-guards (re-exported from api-client)
- DRY fetchIssueAlertRuleJson / fetchMetricAlertRuleJson for get*Rule and get*Document
- Re-exported from api-client, used by both rule-resolve modules

Made-with: Cursor
Implement full alert CRUD coverage for issue and metric rules by adding create commands, broadening merge-based edit support, and updating API helpers, tests, and generated command docs.

Made-with: Cursor
Tighten alert create/edit typing to satisfy strict TypeScript and lint checks, and regenerate related skill reference docs so CI passes consistently.

Made-with: Cursor
Compute month-boundary period examples in UTC so generated skill/docs output does not drift between local environments and CI.

Made-with: Cursor
@MathurAditya724 MathurAditya724 marked this pull request as ready for review April 24, 2026 01:21
MathurAditya724 and others added 2 commits May 26, 2026 20:01
Resolve conflicts:
- contributing.md: keep both main's new commands (local, replay, archive, import, revisions, restore) and branch's alert entry
- api-client.ts: keep both discover exports (main) and error-guards + apiRequestToRegionNoContent exports (branch)
- infrastructure.ts: keep main's throwRawApiError refactor and branch's apiRequestToRegionNoContent (updated to use throwRawApiError)
- resolve-target.ts: keep both branch's resolveTargetsFromParsedArg and main's resolveOrgOptionalProjectTarget
Copy link
Copy Markdown
Contributor

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

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

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit d4838b5. Configure here.

Comment thread src/commands/alert/issues/list.ts Outdated
Comment thread src/commands/alert/issues/edit.ts Outdated
Comment thread src/commands/alert/issues/list.ts
Comment thread src/commands/alert/metrics/delete.ts
Comment thread src/commands/alert/metrics/list.ts Outdated
Comment thread src/commands/alert/metrics/list.ts Outdated
- Replace bun:test imports with vitest in all 8 alert test files
- Fix empty-page pagination hint being dropped in issues/list and
  metrics/list (move moreHint build before the empty-page early return)
- Fix edit command validating existing rule conditions/actions instead
  of only user-provided flags
- Fix parseMetricRuleArg misrouting bare org slugs to project-search
  by appending trailing slash for org-all mode
- Fix safety-cap path in fetchRulesForOrg returning hasMore:false and
  dropping the cursor
- Fix transient fetch failure permanently excluding org from pagination
  by using start-of-list cursor as fallback
Comment on lines +134 to +144
validateMetricTimeWindow(Number(body.timeWindow ?? 0));
if (typeof body.query !== "string" || body.query.trim() === "") {
throw new ValidationError("query must be present and non-empty.", "query");
}
if (typeof body.aggregate !== "string" || body.aggregate.trim() === "") {
throw new ValidationError(
"aggregate must be present and non-empty.",
"aggregate"
);
}
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Bug: The validation for editing metric alerts uses an invalid fallback value of 0 for timeWindow. If the API response lacks this field, the edit operation will always fail.
Severity: MEDIUM

Suggested Fix

The validation logic in validateMetricBody should be adjusted for edit operations. Instead of validating the entire merged document with strict requirements, it should only validate the fields explicitly provided by the user via flags. Alternatively, if validating the whole document is intended, remove the invalid fallbacks like ?? 0 for timeWindow and handle missing fields from the API response more gracefully, perhaps by not validating them if they weren't part of the user's update.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: src/commands/alert/metrics/edit.ts#L129-L144

Potential issue: When editing a metric alert rule, the code fetches the existing rule's
data, merges it with user-provided flags, and then validates the merged object. The
validation for `timeWindow` uses a fallback of `0` (i.e., `body.timeWindow ?? 0`).
However, `0` is not a valid value according to `validateMetricTimeWindow`. If the API
response for an existing rule is missing the `timeWindow` field for any reason, the
validation will use the invalid fallback `0` and throw a `ValidationError`, causing the
edit operation to fail. A similar issue exists for `triggers`, which would fail
validation if missing from the API response.

Comment on lines +148 to +156
},
async *func(this: SentryContext, flags: CreateFlags, arg: string) {
const { cwd } = this;
if (!flags.name.trim()) {
throw new ValidationError("Rule name cannot be empty.", "name");
}
if (!flags.query.trim()) {
throw new ValidationError("query cannot be empty.", "query");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Bare org slug resolves as project-search, silently targeting the wrong organization

Passing my-org (no trailing slash) to sentry alert metrics create my-org ... causes parseOrgProjectArg to return { type: "project-search", projectSlug: "my-org" }. If a project named my-org exists in a different organization, resolveTargetsFromParsedArg returns that org's slug, and the metric alert is created in the wrong organization without any error. The usage hint and docs examples show <org> but the correct form is <org>/ to trigger org-all parsing.

Evidence
  • parseOrgProjectArg docs (arg-parsing.ts:568-569) show "cli"{ type: "project-search", projectSlug: "cli" }; only "sentry/" (trailing slash) yields { type: "org-all", org: "sentry" }.
  • resolveTargetsFromParsedArg project-search branch calls findProjectsBySlug(parsed.projectSlug) and maps results to { org: m.orgSlug, ... } (resolve-target.ts:1912-1915).
  • The metrics create command then does targets.map(t => t.org) — if a project named acme-corp lives in other-org, orgSlugs becomes ["other-org"] and the alert is POSTed to the wrong org.
  • The fallback isOrg error check (resolve-target.ts:1878) only fires when findProjectsBySlug returns zero matches and the slug is an org; a coincidental project name match bypasses it entirely.
  • The docs and USAGE_HINT on line 23 both show <org> not <org>/, so users will consistently trigger this path.
Also found at 1 additional location
  • src/app.ts:88

Identified by Warden find-bugs · LMM-4VA

Comment on lines +94 to +121
if (isAllDigits(ref)) {
const hits: IssueRuleResolution[] = [];
for (const target of targets) {
try {
const rule = await getIssueAlertRule(target.org, target.project, ref);
hits.push({ target, rule });
} catch (e) {
if (isNotFoundApiError(e)) {
continue;
}
throw e;
}
}

if (hits.length === 1) {
return hits[0] as IssueRuleResolution;
}
if (hits.length > 1) {
throw new ValidationError(
`Alert rule ID '${ref}' matched multiple projects.\n` +
"Use an explicit target: sentry alert issues <command> <org>/<project>/<rule-id>"
);
}
throw new ResolutionError(
`Issue alert rule '${ref}'`,
"not found",
usageHint
);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

All-digit rule names silently resolve to a different rule by ID, or fail to resolve at all

When ref is all digits, resolveIssueAlertRule only performs an ID-based API lookup and never falls back to name-based search. A rule whose name happens to be all digits (e.g. "42") will either silently return a different rule (the one with numeric ID 42), or return "not found" if no rule with that ID exists — even though the named rule does exist.

Evidence
  • isAllDigits(ref) at line 94 branches unconditionally: the true path calls getIssueAlertRule(target.org, target.project, ref) (an ID lookup) for every target and returns early or throws without ever reaching the name-based loop at line 122.
  • getIssueAlertRule in src/lib/api/alerts.ts:102 forwards ruleId directly to the API endpoint — it treats any digit string as a numeric ID, not a name.
  • If a project has both rule ID 42 and a distinct rule named "42", the caller supplying "42" always gets rule ID 42, silently bypassing the intended target.
  • The same design exists in the metrics sibling (src/commands/alert/metrics/rule-resolve.ts:87) but is out of this hunk's scope.

Suggested fix: After the ID-based search finds zero matches, fall back to the name-based loop instead of immediately throwing ResolutionError.

Suggested change
if (isAllDigits(ref)) {
const hits: IssueRuleResolution[] = [];
for (const target of targets) {
try {
const rule = await getIssueAlertRule(target.org, target.project, ref);
hits.push({ target, rule });
} catch (e) {
if (isNotFoundApiError(e)) {
continue;
}
throw e;
}
}
if (hits.length === 1) {
return hits[0] as IssueRuleResolution;
}
if (hits.length > 1) {
throw new ValidationError(
`Alert rule ID '${ref}' matched multiple projects.\n` +
"Use an explicit target: sentry alert issues <command> <org>/<project>/<rule-id>"
);
}
throw new ResolutionError(
`Issue alert rule '${ref}'`,
"not found",
usageHint
);
// No ID match — fall through to name-based resolution below

Identified by Warden find-bugs · 75B-EM3

Comment on lines +211 to +213
return { hint: "Dry run - no metric alert rule was created." };
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Status check defaults to 'disabled' when API omits status field on newly created metric alert

When created.status is undefined (absent from the API response), created.status === 0 || created.status === "0" evaluates to false, so the rule is reported as "disabled" even though a freshly created rule should be "active". Invert the check to === 1 to default to "active" for unknown values, matching the MetricAlertRule type comment (0 = active, 1 = disabled) and the pattern used in issues/create.ts.

Evidence
  • createMetricAlertRule returns Promise<Record<string, unknown>>, so created.status is typed as unknown and may be absent.
  • MetricAlertRule type (alerts.ts:42) documents 0 = active, 1 = disabled; the only truthy-disabled value is 1, not any non-zero.
  • The sibling issues/create.ts uses String(created.status ?? "active"), correctly defaulting absent status to "active".
  • metrics/edit.ts uses the identical inverted check, confirming the same bug is present there too.
  • If the Sentry API omits status in the POST response, every created rule is silently shown as disabled.

Identified by Warden find-bugs · 5ZS-L6G

Comment on lines +75 to +83
brief: "Open metric alert rules page in browser",
default: false,
},
},
aliases: { w: "web" },
},
async *func(this: SentryContext, flags: ViewFlags, arg: string) {
const { cwd } = this;
const { ref, targetArg } = parseMetricRuleArg(arg, USAGE_HINT);
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

resolveTargetsFromParsedArg throws misleading 'no accessible projects' error for org-scoped metric alert view

When the user runs sentry alert metrics view my-org/12345, parseMetricRuleArg appends a trailing slash to produce targetArg = "my-org/", which parseOrgProjectArg converts to { type: "org-all" }. resolveTargetsFromParsedArg then calls listProjects("my-org") and throws ResolutionError: Organization 'my-org' has no accessible projects if the org has no accessible projects — even though metric alerts are org-scoped and require no project at all.

Evidence
  • parseMetricRuleArg("my-org/12345") sets targetPart = "my-org" and appends /, yielding targetArg = "my-org/" (rule-resolve.ts line 57).
  • parseOrgProjectArg("my-org/") returns { type: "org-all", org: "my-org" } (arg-parsing.ts).
  • resolveTargetsFromParsedArg for org-all calls listProjects(parsed.org) and throws ResolutionError: Organization 'my-org' has no accessible projects when the project list is empty (resolve-target.ts lines 1835–1849).
  • Metric alert rules are org-scoped (no project needed), so the listProjects call is unnecessary and the resulting error is misleading.
  • The same pattern exists in metrics/delete.ts and metrics/edit.ts, so the fix should apply consistently across those files too.

Identified by Warden find-bugs · GJT-644

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: add alert commands

2 participants