Skip to content

fix(api): centralize 403 Forbidden enrichment with actionable hints (CLI-1JG)#892

Merged
BYK merged 7 commits into
mainfrom
fix/centralized-403-enrichment
Apr 30, 2026
Merged

fix(api): centralize 403 Forbidden enrichment with actionable hints (CLI-1JG)#892
BYK merged 7 commits into
mainfrom
fix/centralized-403-enrichment

Conversation

@BYK
Copy link
Copy Markdown
Member

@BYK BYK commented Apr 30, 2026

Summary

  • Centralize 403 Forbidden error enrichment in throwApiError() / throwRawApiError() so all 60+ API endpoints get actionable error messages instead of the raw "You do not have permission to perform this action."
  • For env-var tokens: extract specific missing scope names from the API response and link to https://sentry.io/settings/auth-tokens/
  • For OAuth tokens: suggest re-authentication with sentry auth login
  • Add enriched403 flag to ApiError to prevent double-enrichment where command-layer code already has specialized 403 handling (issue list, dashboard, project delete)

Fixes CLI-1JG — a user hit sentry org view with an auto-detected org and got a completely unhelpful 403 error.

Before

Error: Failed to get organization: 403 Forbidden
  You do not have permission to perform this action.

After (env-var token)

Error: Failed to get organization: 403 Forbidden
  You do not have permission to perform this action.

  Your SENTRY_AUTH_TOKEN token may lack the required scope for this operation.
  Check token scopes at: https://sentry.io/settings/auth-tokens/

After (OAuth token)

Error: Failed to get organization: 403 Forbidden
  You do not have permission to perform this action.

  You may not have access to this resource.
  Re-authenticate with: sentry auth login

BYK added 3 commits April 29, 2026 17:59
Two bugs caused 400 Bad Request errors from the Sentry API:

1. listTransactions/listSpans passed --limit directly as per_page.
   Values >100 were rejected by the API. Now caps at API_MAX_PER_PAGE
   and auto-paginates when limit exceeds 100, matching the queryEvents
   pattern in discover.ts. (CLI-F3, CLI-10)

2. timeRangeToApiParams returned only start or only end for open-ended
   absolute ranges (e.g. --period '>=2024-01-01'). The API requires
   both. Now fills end=now for start-only, start=end-90d for end-only.
   Fixes all consumers (traces, spans, explore). (CLI-10)
Extract the duplicated multi-page pagination loop from listTransactions,
listSpans, and queryEvents into a generic autoPaginate<T>() helper in
infrastructure.ts. This addresses Cursor Bugbot's feedback about code
duplication across the three call sites.

Also adds comprehensive tests for listTransactions and listSpans (26 tests
covering URL construction, per_page capping, auto-pagination, trimming,
sort, project handling, and cursor passthrough) to push patch coverage
above 80%.
…LI-1JG)

Previously only 4 of 60+ API call sites enriched 403 errors with
scope/token guidance. All other endpoints surfaced a raw unhelpful
'You do not have permission to perform this action.' message.

Add enrich403Detail() in infrastructure.ts as a centralized chokepoint
that enriches all 403 ApiErrors with:
- Env-var tokens: specific missing scopes + token settings URL
- OAuth tokens: re-authentication suggestion

Add enriched403 flag to ApiError to prevent double-enrichment where
command-layer code already has specialized 403 handling.

Remove redundant enrichListOrgsForbidden() from organizations.ts.
Guard issue list's build403Detail() to skip scope hints when
centralized enrichment already applied.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 2026

PR Preview Action v1.8.1

QR code for preview link

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

Built to branch gh-pages at 2026-04-30 20:27 UTC.
Preview will be ready when the GitHub Pages deployment is complete.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented Apr 30, 2026

Codecov Results 📊

6410 passed | Total: 6410 | Pass Rate: 100% | Execution Time: 0ms

📊 Comparison with Base Branch

Metric Change
Total Tests 📈 +7
Passed Tests 📈 +7
Failed Tests
Skipped Tests

All tests are passing successfully.

✅ Patch coverage is 83.76%. Project has 13189 uncovered lines.
✅ Project coverage is 76.02%. Comparing base (base) to head (head).

Files with missing lines (5)
File Patch % Lines
src/commands/issue/list.ts 13.33% ⚠️ 13 Missing
src/lib/api/infrastructure.ts 90.29% ⚠️ 10 Missing
src/commands/dashboard/resolve.ts 28.57% ⚠️ 5 Missing
src/lib/api/traces.ts 96.15% ⚠️ 2 Missing
src/lib/time-range.ts 77.78% ⚠️ 2 Missing
Coverage diff
@@            Coverage Diff             @@
##          main       #PR       +/-##
==========================================
+ Coverage    75.95%    76.02%    +0.07%
==========================================
  Files          295       295         —
  Lines        55028     55009       -19
  Branches         0         0         —
==========================================
+ Hits         41794     41820       +26
- Misses       13234     13189       -45
- Partials         0         0         —

Generated by Codecov Action

… enrichment

When the API returns { detail: null } or { detail: undefined },
stringifyUnknown() would produce the literal string 'undefined' or
'null', which leaked into the enriched error message. Now falls back
to stringifying the whole error object instead.

Also add tests for undefined/null detail edge cases and fix env var
cleanup symmetry in OAuth test.
Comment thread src/lib/api/infrastructure.ts
BYK added 2 commits April 30, 2026 19:53
…nriched

When centralized 403 enrichment has already added scope/token hints,
re-throw the ApiError directly instead of passing the multi-line
enriched detail into build403Error, which would nest it as a single
messy bullet in a ResolutionError.
…lish

When throwApiError receives { detail: undefined } or { detail: null },
the fallback stringifyUnknown(error) produces "{}" or {"detail":null}
which leaked as the first visible line before the enrichment hints.

Now passes undefined to enrich403Detail for 403s with nullish detail,
so the enrichment stands alone without a noisy prefix. Non-403 errors
keep the fallback behavior for debugging.
Comment thread src/lib/api/infrastructure.ts
The nullish detail fix from the previous commit only covered
throwApiError (SDK path). throwRawApiError (raw fetch path via
apiRequestToRegion) had the same issue: { detail: null } would
produce {"detail":null} as visible output in 403 enrichment.

Now checks typeof parsed.detail before using it, and skips the
JSON.stringify fallback for 403 errors.
Comment thread src/lib/time-range.ts
@BYK BYK merged commit e37329b into main Apr 30, 2026
26 checks passed
@BYK BYK deleted the fix/centralized-403-enrichment branch April 30, 2026 20:40
BYK added a commit that referenced this pull request Apr 30, 2026
…#897)

## Summary

- Use `getUTCDate()`/`setUTCDate()` instead of `getDate()`/`setDate()`
for the 90-day date backfill in `timeRangeToApiParams()`
- The input is a UTC ISO 8601 string, so date arithmetic should use UTC
methods to avoid off-by-one-day errors in non-UTC timezones

Flagged by Cursor Bugbot on #892
([comment](#892 (comment))).
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.

1 participant