Skip to content

feat(cli): Complete issue short IDs and dashboards in shell completion - #1572

Open
sentry-junior[bot] wants to merge 4 commits into
mainfrom
feat/cli-complete-issues-dashboards
Open

sentry-junior[bot] wants to merge 4 commits into
mainfrom
feat/cli-complete-issues-dashboards

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Oct 21, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #1563.

Shell completion only suggested org slugs, org/project pairs and aliases. It now also suggests issues and dashboards the user has already seen, still without any API call in sentry __complete.

Where the values come from. The issue and dashboard API functions record what they return in a new completion_cache SQLite table (schema v20, columns kind, org_slug, value, label). Issues are stored by short ID, dashboards by numeric ID, both with the title as label. Hooks: listIssuesPaginated, getIssueInOrg, getIssueByShortId, listDashboardsPaginated, getDashboard, createDashboard. The write goes through src/lib/api/completion-seed.ts, which reports and swallows errors so a cache problem can't fail the command. I used a table instead of the HTTP response cache: reading HMAC-keyed JSON files of 50–500 KB on every TAB would be far too slow.

What gets completed. Two new positional parsers, following #1557:

  • parseIssueTarget, used by issueIdPositional, issueIdsPositional and issue merge (so issue view/resolve/explain/plan/... and event list). It keeps the org/project suggestions and adds CLI-1A2 short IDs. After org/ it suggests org/CLI-1A2. A short ID that exists in several orgs is written as org/ID.
  • parseDashboardTarget, used by dashboard view/restore/revisions and dashboard widget edit/delete. The first positional gets org/ and org/<id>. After an org positional (dashboard view acme <TAB>), it gets bare IDs for that org.

Titles appear as descriptions, and the partial is matched against them too, so dashboard view err<TAB> can find acme/12 Errors (bash and fish; zsh still filters by value prefix). An empty partial lists the most recently seen entries first, capped at 50.

Flag values. The completion generator also records each target command's value-taking flags (non-boolean flags and their aliases) in completion-targets.json. __complete suggests nothing for a flag's value (dashboard view --period <TAB>, and also the existing issue list --query <TAB>). Dashboard completion skips flag values when counting positionals, so --period 7d isn't counted as an argument.

Bounds and privacy. The cache keeps the 500 newest entries per kind and org. Entries expire with the other caches after 7 days. Only orgs of the active credential are suggested, and the table is cleared on logout like issue_org_cache, because titles depend on the user's permissions.

Not covered:

  • Event IDs: 32-char hex isn't useful to tab through, and nothing caches it cheaply.
  • dashboard widget add: its last positional is a new title, so the positional-counting heuristic would suggest dashboards in the wrong slot.
  • Releases, monitors, teams, replays, traces: adding one now means a kind in db/completion-cache.ts, a seed call, and a parser and completer. packages/cli/AGENTS.md describes this.

via Junior system actor event.

--

View Junior Session in Sentry

Shell completion now suggests issues and dashboards the user has
already seen, not only org and project slugs. Issue and dashboard API
calls (list, get, short-ID lookup, dashboard create) record short IDs,
dashboard IDs, and titles in a new `completion_cache` SQLite table
(schema v20). `sentry __complete` reads it with no API calls.

Positionals opt in with two new parsers in completion-target.ts:
`parseIssueTarget` (issueIdPositional, issue merge) and
`parseDashboardTarget` (dashboard view/restore/revisions and widget
edit/delete). Issue completion keeps the org/project suggestions and
adds short IDs, plus `org/SHORT-ID` after an org prefix. Dashboard
completion suggests `org/<id>` for the first positional and bare IDs
after an org. Titles show as descriptions and are matched too.

The cache keeps the 500 newest entries per kind and org, expires with
the other caches, only suggests orgs of the active credential, and is
cleared on logout.

Refs #1563

Co-Authored-By: byk <burak.kaya@sentry.io>
@vercel

vercel Bot commented Oct 21, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated
sentry-local Skipped Skipped Oct 23, 2026 11:35pm UTC

Request Review

@github-actions github-actions Bot added the risk: medium PR risk score: medium label Oct 21, 2026

@cursor cursor Bot left a comment •

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.

Stale Bugbot comment from a previous run.

Comment thread packages/cli/src/lib/complete.ts
`__complete` treated the word after a value-taking flag as a target, so
`dashboard view --period <TAB>` suggested dashboards, and `--period 7d`
counted `7d` as a positional.

The completion generator now also records each target command's
value-taking flags (non-boolean flags and their aliases) next to the
target sets in completion-targets.json. `__complete` returns nothing
when the word being completed is such a flag's value, and dashboard
completion counts positionals with flag values removed. This also stops
org/project suggestions after flags like `issue list --query`.
BYK added a commit that referenced this pull request Oct 23, 2026
…1594)

Refs #1564. This is the first slice: the mechanism plus five commands.

A `[<org>/][<project>]` positional now owns both parsing and resolution.
The command picks a parser from `src/lib/target-positional.ts`, and that
parser comes with its resolver:

```ts
parse: orgOptionalTarget,                    // resolveOrgOptionalTarget
parse: projectBoundTarget,                   // resolveProjectBoundTarget
parse: orgProjectTarget(resolveViewTargets), // command-specific resolver
```

`func` gets a `LazyPositional` with three members: `target.parsed` (the
`ParsedOrgProject`), `target.resolved` (a promise of the resolver's
result), and `target.raw` (what the user typed):

```ts
async *func(this: SentryContext, flags: ListFlags, target: OrgOptionalTarget) {
  const resolved = await target.resolved;
```

`func` never calls `parseOrgProjectArg` or a resolver, never picks which
resolver to use, and needs no default for an omitted target. An omitted
optional target arrives as auto-detect. `project view` moves its target
switch into `resolveViewTargets` and reads `target.parsed.type`.

- **No command names to pass.** Resolvers read the command path from
`this.commandPrefix`, which Stricli's `forCommand` sets and the SDK
invoker now sets from its command path. The `COMMAND_NAME` constants
these commands used are gone. A context without `commandPrefix` makes
resolution throw rather than print a hint with a blank command name;
test mock contexts set it.
- **Destructive commands guard automatically.** `buildDeleteCommand`
calls `markDestructive` on its lazy positionals, and the target resolver
then runs `requireExplicitTarget`. `project delete` uses plain
`projectBoundTarget`. Once #1626's `destructive` flag lands, the call
can move into `buildCommand` so `issue merge` and `trial start` get it
too.
- **Lint rule.** `sentry-cli/require-target-resolution` fails a command
function that takes `OrgOptionalTarget`, `ProjectBoundTarget` or
`OrgProjectTarget<R>` but never reads `.resolved`, since skipping it
skips the resolver and the destructive guard.

**When things run.** Both steps are lazy (`LazyPositional` in
`src/lib/positional-parse.ts`, a plain class with memoized getters). The
parse runs on the first `parsed` read and the resolver on the first
`resolved` read; each runs once. Both happen inside `func`, so the order
matches main:

- The auth guard and rc URL check run first. A bad target with no login
still reports "Not authenticated" (exit 10).
- Parse errors keep their `CliError` semantics. `sentry project view
@latest` exits 21 with the `ValidationError`, not Stricli's `Failed to
parse …` (exit -4). A parse error also rejects `resolved`.
- `parseOrgProjectArg`'s URL side effect still runs after those checks.
For a pasted Sentry URL it calls `applySentryUrlContext`, which can
switch `SENTRY_URL`/`SENTRY_HOST`, save that host as the default, or
throw `HostScopeError`. Running it at Stricli scan time would have let
the auth guard and rc URL check run against the host taken from the URL.

Resolution stays a promise because resolvers need auth, config, DSN
detection, and API calls. Shared code that handles any positional
(`buildListCommand`'s plural-alias interception, `help` recovery,
`setArgsContext`) reads the raw input with `rawPositional(value)`, an
`instanceof LazyPositional` check. Stricli calls parsers with the
command context as `this`; the resolver gets that context plus
`commandName`.

**Callers that skip Stricli.** The typed SDK (`buildInvoker`) and tests
that call `command.loader()` pass raw strings. `buildCommand` runs
`applyLazyPositionals` on them, and on omitted optional positionals, so
every caller keeps passing what the user typed and `func` always gets
the same shape. When plural-alias interception drops the target,
`buildListCommand` runs it again so the dropped target becomes
auto-detect. `sdk.project.view({ orgProject: "@latest" })` is rejected
instead of silently auto-detecting. The SDK invoker also sets
`directInvoke`, which turns off `help` positional recovery: a failing
SDK call whose target is `help` throws its error, as on main, instead of
returning help text.

**Shell completion is unchanged.** Completion kinds come from a tag on
the parser (`withCompletionTarget`), shared with `parseOrgTarget` and
`parseOrgProjectTarget`. The generated targets file matches main.

Migrated: `trace list`, `replay list`, `feedback list`, `project view`,
`project delete`.

Not in this PR:
- `LIST_TARGET_POSITIONAL` / `buildOrgListCommand`. #1575
(`splitListTargetAndFilter`) reads the raw target and splits it before
parsing (for example `acme/web-*`). These should move after it lands,
using `target.raw`.
- Issue and dashboard positionals. #1572 adds `parseIssueTarget` /
`parseDashboardTarget` in the same files. When one of the two PRs
rebases, its parsers should switch from the `PARSER_TARGETS` map to
`withCompletionTarget`.
- `[<org>/<project>/]<id>` positionals (`trace view`, `span list`,
`event view`, `log view`, …). Stricli parses each argument on its own,
but what each one means depends on how many were given. Those keep
parsing in `func`.
- Release `org/version` positionals. They already share
`resolveReleaseTarget`.

<!-- junior-request-attribution:start -->
via **BYK**.
<!-- junior-request-attribution:end -->

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:slack%3AC010884QHLG%3A1792667769.623489 -->

--

[View Junior
Session](https://junior-prod.sentry.dev/conversations/slack%3AC010884QHLG%3A1792667769.623489)
[[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3AC010884QHLG%3A1792667769.623489/?project=4510944073809921)

<!-- junior-session-footer:end -->

---------

Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
Co-authored-by: Burak Yigit Kaya <burak.kaya@sentry.io>
main now tags completion parsers with withCompletionTarget instead of a
lookup map, so parseIssueTarget and parseDashboardTarget use that tag too.
The CLI AGENTS.md keeps main's target positional section and adds the
issue and dashboard completion notes.

setAuthToken is synchronous on main, so the clearAuth test no longer
awaits it. cacheCompletionEntities now ranks each batch above stored
entries, so two writes in the same millisecond keep the newest first.

Co-Authored-By: BYK <burak.kaya@sentry.io>

@cursor cursor Bot left a comment

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.

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 1a2405d. Configure here.

Comment thread packages/cli/src/lib/complete.ts
`issue link` and `issue unlink` take `<issue> <url>`, but `__complete`
suggested issue short IDs for the URL too, because issue completion
never looked at how many positionals were already typed.

The completion generator now records, for commands with a fixed list of
positionals, which positions take the target. Issue completion returns
nothing for any other position. Variadic commands such as `issue view`
and `issue merge` still complete every positional.

This branch was previously deployed

1 inactive deployment
Preview — 8c6b2d16 Deployed Oct 23, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

risk: medium PR risk score: medium

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Complete more entity types in shell completion

0 participants