Add an exact-slug Sentry project resolver to the sentry-triage canvas (#2819)

* Fix sentry-triage canvas crash when the `sentry` package isn't bundled

Published awesome-copilot plugins ship extension source only, so the optional
`sentry` npm package the canvas depends on at runtime may be absent. Previously
that made the canvas crash on open instead of guiding the user through setup.

- Load the optional `sentry` package lazily and translate only the top-level
  ERR_MODULE_NOT_FOUND for `sentry` into a package-missing setup state; any other
  import failure (missing transitive dep, entrypoint throwing) is rethrown so a
  real defect isn't masked behind a misleading "reinstall" message.
- Add a dedicated package-missing branch to the connection preflight and a
  matching setup gate, kept distinct from the auth and transient-network gates so
  the user never sees contradictory guidance. The canvas now opens and explains
  what to do rather than crashing.
- Clear `configured` for the package-missing state so the status is no longer the
  contradictory `configured:true` + `setup:'package-missing'`.
- Tell users to sign in with the package-local CLI via `npx sentry auth login`
  run from the extension folder — the only form that resolves after a local
  `npm install`, since a package-local binary isn't on the shell PATH.
- Update the README so the sign-in step and install guidance cover the
  published-plugin layout (`com.github.copilot/extensions/sentry-triage`), not
  just the standalone user/project extension paths.

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

* Add an exact-slug Sentry project resolver to the sentry-triage canvas

The project picker previously only offered projects from the paged list the
canvas had already loaded. Teams with many projects (or a project outside the
first page) had no way to target one by slug. This adds a verify-on-commit
resolver: when a user types a slug that isn't a local match, pressing Enter
checks it against Sentry and only commits the canonical slug once verified,
so a scan never runs against an unverified or wrong-org project.

Canvas / UX (components/page.mjs, styles.mjs):
- Autocomplete accepts an exact slug not in the local list; Enter is the
  explicit commit that triggers resolution (never an as-typed lookup).
- A visually-hidden aria-live region announces checking / verified / not
  found / couldn't-check state, and the resolved state is rendered before
  commit so screen readers hear the outcome.
- Footer/menu surfaces checking, prompt, missing, and error states, including
  when local partial matches are present.
- Project choices are read from an org-keyed cache so a slug from a previously
  selected org can never be treated as local after a free-text org switch;
  the stale-completion guard also compares the org captured for the request.

Server / resolution (server.mjs, sentry.mjs, sentryClient.mjs, extension.mjs):
- CSRF-gated /api/resolve-project verifies a single slug against Sentry.
- Resolution runs on the shared serial request chain and is hardened against
  Sentry outages and queue contention (transient errors are retryable, a
  confirmed miss is cached as "missing").

Also bumps sentry-triage to 1.1.0 (package.json, plugin.json,
marketplace.json).

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>

---------

Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
This commit is contained in:
Liz Tom
2026-08-27 11:36:09 +10:00
committed by GitHub
co-authored by Copilot App
parent c72fb21484
commit 36f9fe7883
9 changed files with 455 additions and 84 deletions
+20 -3
View File
@@ -284,8 +284,13 @@ async function listProjectsPaged(org, onPage) {
// Verify a specific project slug exists / is accessible. The org's project list
// is capped, so a valid slug may not appear in it; project view resolves any
// slug directly. Returns the canonical slug, or '' if it doesn't exist / isn't
// accessible.
// slug directly. Returns the canonical slug on success, or '' ONLY when the
// project is confirmed not to exist. A transient/permission failure (network,
// rate limit, 403, 5xx) is NOT a "not found": it re-throws so the caller can
// report "couldn't check" instead of a false "no such project" that would train
// the user to distrust a correct slug. Runs on the shared serial SDK chain
// (projectView); interactive callers invoke it only on an explicit commit, so it
// never floods that queue.
export async function findProject(org, slug) {
const wanted = String(slug || '').trim()
if (!wanted) return ''
@@ -294,11 +299,23 @@ export async function findProject(org, slug) {
const resolved = String(project?.slug || '').trim()
return resolved || wanted
} catch (err) {
if (err instanceof SentryError) return ''
if (err instanceof SentryError && isProjectNotFound(err)) return ''
throw err
}
}
// Decide whether a failed project.view means the project genuinely does not
// exist (a confirmed 404 / "not found"), as opposed to a failure that merely
// prevented the check. A definite non-404 HTTP status (403/429/5xx) is never a
// not-found; only an explicit 404 or an unambiguous not-found message counts.
function isProjectNotFound(err) {
const info = sentryErrorInfo(err)
if (info && info.code) return info.code === 404
const t = `${err?.message || ''}\n${err?.stderr || ''}`
if (/permission|forbidden|not authorized|unauthorized/i.test(t)) return false
return /\bnot found\b|no such project|does(?:n't| not) exist|unknown project/i.test(t)
}
// Per-query issue search. `limit` bounds a single call; the SDK auto-pages up to
// the SDK max (1000) to satisfy it. The primary board search and the targeted
// regression/escalation searches all pass an explicit bounded cap so an org with