Repository navigation
feat(cli): Complete issue short IDs and dashboards in shell completion - #1572
Open
sentry-junior[bot] wants to merge 4 commits into
Open
sentry-junior[bot] wants to merge 4 commits into
sentry-junior[bot] wants to merge 4 commits into
Conversation
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>
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
`__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>
Contributor
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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.
`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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.

Fixes #1563.
Shell completion only suggested org slugs,
org/projectpairs and aliases. It now also suggests issues and dashboards the user has already seen, still without any API call insentry __complete.Where the values come from. The issue and dashboard API functions record what they return in a new
completion_cacheSQLite table (schema v20, columnskind,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 throughsrc/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 byissueIdPositional,issueIdsPositionalandissue merge(soissue view/resolve/explain/plan/...andevent list). It keeps the org/project suggestions and addsCLI-1A2short IDs. Afterorg/it suggestsorg/CLI-1A2. A short ID that exists in several orgs is written asorg/ID.parseDashboardTarget, used bydashboard view/restore/revisionsanddashboard widget edit/delete. The first positional getsorg/andorg/<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 findacme/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.__completesuggests nothing for a flag's value (dashboard view --period <TAB>, and also the existingissue list --query <TAB>). Dashboard completion skips flag values when counting positionals, so--period 7disn'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:
dashboard widget add: its last positional is a new title, so the positional-counting heuristic would suggest dashboards in the wrong slot.db/completion-cache.ts, a seed call, and a parser and completer.packages/cli/AGENTS.mddescribes this.via Junior system actor
event.--
View Junior Session in Sentry