Repository navigation
ref(cli): Ratchet hand-parsed positionals and test argument conventions - #1739
Merged
Merged
Conversation
Commands that still parse a positional inside `func` are now on an allow-list in the lint plugin, each with its follow-up, so the remaining ones are named and no new one can appear. - `require-target-resolution` also rejects the completion-only target parsers (`parseOrgTarget`, `parseOrgProjectTarget`), `parseOrgProjectArg`, and the `resolve*FromArg` helpers in commands. - `no-ad-hoc-id-arguments` also rejects the completion-only `parseIssueTarget` and `parseDashboardTarget`. - Both allow-lists are ratchets: an entry whose file stopped parsing by hand fails until it is removed. Release commands take `[<org>/]<version>` through `releaseParameter`, a bound positional: a blank version fails in the argument scanner (exit 252), and `func` awaits `target.resolved` for the org. `test/lib/argument-conventions.test.ts` feeds the same inputs through every command that takes trace, span, log, replay, issue, or release arguments, using the command's own positional, and encodes where a command legitimately differs. Part of #1564. Co-Authored-By: BYK <burak.kaya@sentry.io>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
BYK
approved these changes
Oct 29, 2026
sentry-junior Bot
added a commit
that referenced
this pull request
Oct 29, 2026
…get-parser Conflicts: packages/cli/AGENTS.md (kept both this branch's dashboard note and main's argument-conventions note; dropped parseDashboardTarget, which this branch removes, from main's list of completion-only parsers). Also removed the dashboard entries from the #1739 allow-lists in packages/cli/lint-rules/cli-oxlint-plugin.js, since these commands now parse before func. Co-Authored-By: BYK <burak.kaya@sentry.io>
BYK
added a commit
that referenced
this pull request
Oct 29, 2026
…mar (#1730) Part of #1564. Dashboard commands parsed their positionals by hand inside `func`, and issue commands used their own grammar (#1729). This moves both onto the shared ID grammar (`grammar/id-arguments.pegjs`, #1714), parsed before `func` runs. Event view's issue checks (#1733, #1738) use the new issue kind too. **Shared grammar: kinds with names.** `IdKind.names` adds one idea to `id-arguments.pegjs`: a kind may name an item without its ID, and anything that is not, and may not be, an ID is a name. Kinds without names keep their grammar paths exactly as before. Kinds with names keep each line whole (no splitting at spaces), so `Top 10` stays one title and never becomes title `Top` + ID `10`. - `ID_KINDS.dashboard`: a numeric ID or a title, which may hold spaces and slashes (`Latency/p95`). `<org>/[<project>/]<digits>` reads as a path only when slugs come before the digits, so `Errors.v2/123` and `/123` are titles. After a target, the title takes the rest of the arguments (`my-org/ My Dashboard`). A bare first word names an org (`orgScoped`). - `ID_KINDS.issue`: an identifier (numeric ID, short ID, selector, issue URL) is one whole token (`wholeIds`). `src/lib/issue-identifier.ts` takes it apart with the #1729 grammar, which is unchanged. Text with spaces is a name, meaning a search. - `dashboard-arguments.pegjs` is gone. `dashboard list` keeps its thin `parseDashboardListArgs`, because a bare word there is a title filter (see #1711). **Bare first word: org or title?** For `dashboard view My Dashboard`, the parser makes no decision. It returns the structural reading (org `My`, title `Dashboard`) and the whole input as one title (`nameWithTarget`, surfaced as `orgOrTitle`). `resolveDashboardInput` decides after auth: - First it checks whether `My` is an org: the cached org list and region cache, then `GET /organizations/My/` only on a cache miss. - If the org exists, the title lookup in that org is final, even when it misses. - On a 404 or 403 it warns ``No org 'My'; treating "My Dashboard" as the dashboard title. Use my-org/ to name an org.`` and looks up "My Dashboard" in the auto-detected org. - If that misses too, one error names both readings (no org `My` to find `Dashboard` in, and no `My Dashboard` in `<detected-org>`) and hints at `<org>/ Dashboard` and at quoting. - A 5xx or network error from the org check propagates, with no fallback. - It never falls back for `my-org/ …` (explicit org), a numeric ref, or a URL. **Issue search text.** `issue view "login timeout"` resolves through `GET /organizations/{org}/issues/?query=…` in the detected org. One hit is the issue. Zero or several hits raise a `ResolutionError` that lists up to 5 candidates as runnable commands and points at `sentry issue list <org>/ --query '…'`. I checked the endpoint against getsentry/sentry (`organization_group_index.py`, `group_index/index.py`): `query` takes free text, an exact event ID or short ID gets a fast lookup, and an explicit query replaces the default `is:unresolved`. **Search hints.** Text where a trace, span, log, or replay ID belongs (`trace view login`) now fails with ``Not a trace ID: "login". To search, try `sentry trace list --query 'login'`.``. The hint comes from `validate()`, so commands that recover malformed IDs (`trace view`, `span view`, `log view`) recover first and use the hint only as their final error. Event IDs get no hint, because `event list` lists one issue's events. **event view.** `ID_KINDS.issue.distinctForm` (uppercase short ID or numeric) replaces `looksLikeIssueShortId` + `isAllDigits` and keeps #1738's order: `<org>/<short-id>`, then `<short-id>/<event-id>` (even an all-digit event ID), then `<org>/<numeric-id>`. I added a regression test for the all-digit event ID and one for that order. **Behavior changes:** - `dashboard view my-org My Dashboard` (unquoted title) looks up "My Dashboard". Before, it used `My` and dropped `Dashboard`. The same goes for `my-org/ My Dashboard` and `https://acme.sentry.io/ My Dashboard`. - `dashboard view My Dashboard`: when there is no org `My` (404/403), it falls back to the title "My Dashboard" in the detected org, with the warning above. If neither reading finds a dashboard, one error names both. Dashboard commands with one dashboard ref get this (`view`, `restore`, `revisions`, `widget edit/delete`, and `widget add` after it splits off its widget title). - A numeric ref first is the dashboard, as with every ID kind: `dashboard view 42 X`, `my-org/42 X`. Before, the first of two arguments was always the target. After a target, a numeric ID keeps the rest as extra (`my-org 123 extra` → ID 123, ignored `extra`) instead of joining them into a title. Arguments after the ID go through the shared rules: extra, reversed (`42 my-org/`), or the shared "reference different projects" error when they name another target. - A lone non-Sentry URL is no longer taken as a dashboard title. - `issue view "login timeout"` (text with spaces) searches instead of failing as a malformed identifier. Control characters and URLs are still rejected. - Trace/span/log/replay IDs that are plain text get a search hint instead of "Invalid trace ID". - Parse errors (no dashboard, an org URL without a dashboard, a dashboard URL passed to `list`, an invalid target slug) fail before the auth guard, as in the earlier commits on this branch. They print `Failed to parse "…" for <placeholder>: …` and exit 252. - `widget add/edit/delete`: an argument error comes before flag errors like "Specify --index or --title". `widget add`'s positional completes cached dashboards. `parseDashboardTarget` had no callers left and is removed. **Lint ratchet (#1739):** the dashboard entries are off both allow-lists, since these commands now parse before `func`. The issue entries (`issue/utils.ts`, `issue/merge.ts`, which need an array issue positional) stay. **Syncing:** this branch merges `main` (no rebase). 54ebfa2 and 7f52f19 merge #1729, #1732–#1734, #1736, #1738 and #1739. Verification: - `tsc`, `pnpm run lint` (oxlint, format, ast-grep) and `scripts/cli-lint-rules.test.mjs` (70/70) pass. - vitest on `test/lib`, `test/grammar` and the dashboard, issue, event, trace, span, log, replay and feedback command tests: 10,158 passed. The 6 failures are the sandbox-only permission tests (completions EACCES/EPERM, code-scanner, file-changes, telemetry readonly, upgrade lock), which also fail on `main`. The full CLI suite shows the same 6, plus e2e/eval files that need a build or credentials. - New tests: - The bare-org fallback: the org from cache and from the API (no fallback), org missing then fallback hit with the warning, a 403, org missing then fallback miss with the combined error, a 5xx (propagates, no fallback), `my-org/ My Dashboard`, and a numeric ref. - Issue search with 0, 1 and several hits. - Search hints, including `validate: false` deferral. - #1738's event view order. - Differential runs against this PR's previous parsers: - Issue arguments: 8,575 cases. 1,388 former errors are now searches (text with spaces), with 0 other differences. - Dashboard view: 32,552 cases over 0–3 arguments. Every difference is in one of the categories above: a numeric ID first (2,600), arguments after a dashboard URL that name another target (688), a numeric ID after a target with more words (475), and a lone non-Sentry URL (1). <!-- junior-request-attribution:start --> via **BYK**. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3A5a5e7e4543124325a44d731c08dd6089 --> -- [View Junior Session in Sentry](https://sentry.sentry.io/explore/conversations/agent%3A5a5e7e4543124325a44d731c08dd6089/?project=4510944073809921) <!-- junior-session-footer:end --> --------- Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <burak.kaya@sentry.io>
BYK
pushed a commit
that referenced
this pull request
Oct 29, 2026
…#1742) Part of #1564. Follow-up to #1730 and #1739. #1739 added `argument-conventions.test.ts`, which checks that commands sharing a parser read the same input the same way. #1730 then moved dashboards and issues onto the shared ID grammar, and dashboards and issues weren't in that test yet. This adds them. It only changes `packages/cli/test/lib/argument-conventions.test.ts`. **Dashboard commands:** `view`, `restore`, `revisions`, and `widget add/edit/delete` must all read every `DASHBOARD_INPUTS` entry the same way `dashboard view` does: - Inputs: a bare ID or title, `<org>/<id>`, `<org>/<project>/<id>`, a separate target, `<target> <title words…>`, the bare-org fallback (`My Dashboard`), reversed arguments, extra arguments, and dashboard and org URLs. - Usage-hint text in warnings is normalized before comparing, and `widget add`'s own widget title is removed first. `widget add` only gets inputs of up to 2 arguments, because its last argument is the widget title. - Per command: a missing dashboard fails at parse time, a pasted URL's server is applied on resolve and not on parse, `widget add` splits off its title, and `widget add` rejects more than 3 arguments. **`dashboard list`:** its own block, because it reads a target plus a title filter, not a dashboard. A dashboard URL fails with a suggestion to use `dashboard view`. **Issue commands:** `ISSUE_INPUTS` gains `login timeout`, so every issue command must read search text the same way `issue resolve` does. A separate test checks that it parses as `{ type: "search" }`. No lint allow-list changes: #1730 already removed the dashboard entries. The `issue/utils.ts` and `issue/merge.ts` entries are still in use and get migrated separately. Verification: - `argument-conventions.test.ts`: 336 passed, 19 skipped. - Dashboard, issue and event command suites, plus the id-arguments and dashboard-arguments lib tests: 31 files pass. - `pnpm run lint` in packages/cli is clean, `tsc --noEmit` is clean, and `scripts/cli-lint-rules.test.mjs` passes 70/70. <!-- junior-request-attribution:start --> via **BYK**. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:local%3Aweb%3Aa3773d7739de1b873375a6f1 --> -- [View Junior Session in Sentry](https://sentry.sentry.io/explore/conversations/local%3Aweb%3Aa3773d7739de1b873375a6f1/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
This branch was successfully 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.
Part of #1564.
Every command that still parses a positional inside
funcis now named on a lint allow-list with its follow-up, so the rest can be migrated one by one and no new one can appear. Release commands, the small straggler, move to a bound positional. A new table-driven test checks that commands taking IDs read the same inputs the same way.Lint
no-ad-hoc-id-argumentsnow also rejectsparseIssueTargetandparseDashboardTargetin commands. These parsers only tag a positional for completion and leave the parsing tofunc.require-target-resolutionnow also rejectsparseOrgTarget,parseOrgProjectTarget,parseOrgProjectArg, and theresolve{OrgOnly,OrgOptional,ProjectBound}FromArghelpers in commands.AD_HOC_ID_PARSING_ALLOWED,UNBOUND_TARGET_PARSING_ALLOWED), and each entry names its follow-up. The lists only shrink: an allow-listed file that stops using the names gets "Remove this file from the allow-list".event viewis no longer exempt, since ref(cli): Parse event view arguments with the shared ID grammar #1733 merged.ReleaseTargetwas added toBOUND_TARGET_TYPES.scripts/cli-lint-rules.test.mjs.Release positional
releaseParameter(USAGE_HINT, brief)inrelease/parse.tsreturns a boundReleaseTarget.funcreads{ version, org, detectedFrom }fromawait target.resolved. A blank version now fails the parse (exit 252) instead of failing insidefunc. Migrated commands: archive, create, delete, deploy, deploys, finalize, restore, set-commits, view.Cross-command conventions test
test/lib/argument-conventions.test.tsruns the same inputs through each command's own positional withapplyBoundPositionals, the way Stricli does. The inputs are:<x>/<id><org>/<project>/<id><org>/<project> <id>ArgumentParseError).resolvedruns)Commands covered: trace view/logs, span view/list, log view/list, event view, replay view/download, the single-issue commands (issue and feedback), and all release commands. Where a command legitimately differs, its row says how:
slashTarget: "project"for span view, log view and event viewmultiplefor span view, log view and event viewoptionalfor span list and log listseparateTarget: falsefor span viewAudit: positional parsing in
funcExcludes
event view(#1733, merged) anddashboard/*(#1730).Already on bound parsers (verified)
trace view,trace logs(ref(cli): Parse list command targets in their positional #1711, ref(cli): Parse trace logs and span list arguments before they run #1728)span view,span list(ref(cli): Parse list command targets in their positional #1711, ref(cli): Parse trace logs and span list arguments before they run #1728)log view,log list: fully migrated in ref(cli): Parse every kind of ID argument with one grammar #1714. The remainingparseOrgProjectInputcall is inside the boundparseLogListInput, not infunc.replay view,replay download: fully migrated in ref(cli): Parse every kind of ID argument with one grammar #1714 (parseIdArgumentis called inside the bound parser).issuePositional, ref(cli): Parse issue arguments with their own grammar, before the command runs #1729) andevent view(ref(cli): Parse event view arguments with the shared ID grammar #1733)release *(9 commands): migrated in this PRAllow-listed, still parsing in
funcissue view,issue explain(issue/utils.ts),issue merge: these take several issues and need an array issue positional (Consolidate target parsing into positional parsers #1564)alert issues create/delete/edit/view:[<org>/[<project>/]]<rule>, resolving to every matching project (Consolidate target parsing into positional parsers #1564: alert rule target)alert metrics create/delete/edit/list/view:[<org>/]<rule>, org-scoped (Consolidate target parsing into positional parsers #1564: alert rule target)agent-conversation view,integration delete:[<org>/]<id>with a non-hex ID, the same shape asreleaseParameter(Consolidate target parsing into positional parsers #1564: org-scoped ID)org view,trial list,trial start: a bare org positional (Consolidate target parsing into positional parsers #1564: org positional)init,project create: a target followed by other positionals (Consolidate target parsing into positional parsers #1564)dashboard create,dashboard list/restore/revisions/view,dashboard widget add/delete/edit: covered by ref(cli): Parse dashboard and issue arguments with the shared ID grammar #1730. These entries will fail the ratchet once ref(cli): Parse dashboard and issue arguments with the shared ID grammar #1730 lands, and whichever PR merges second removes them.Not target parsing, and not covered by these rules
monitor run:args[0]is a monitor slug followed by a passthrough command. This is arity handling, not target parsing.api,auth login/switch,build/debug-files/snapshots upload,debug-files find(debug IDs),docs *,help,local *,schema,cli upgrade,org list,react-native xcode,wasm-split.Checks
pnpm run tscandpnpm run lintpass, and the lint-rule tests pass (70/70). The new conventions test and the release and issue tests pass. The only full-suite failures locally are sandbox noise: tests that need to run without root, e2e and init-eval tests that need a build or keys, and timing-sensitive tests.via BYK.
--
View Junior Session in Sentry