Repository navigation
refactor(cli): Build stored command hints lazily and drop asInvocationCommand - #2085
Conversation
…nCommand Commands kept in constants and tables are now LazyCommand thunks (`() => sentry\`issue view <id>\``) that build the hint when it is shown, so nothing rewrites a leading `sentry ` at runtime. ContextError, ResolutionError, buildValidationMessage and org-list print what they get. The sentry tag inserts interpolated values as they are, so user text that starts with "sentry " is no longer rewritten. The lint rule now flags module-level `sentry …` literals too, and a test checks that loading the command tree builds no hints. Refs #2071 Refs #2050 Co-Authored-By: Burak Yigit Kaya <126780+BYK@users.noreply.github.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
|
||
| /** Usage hint for trace mode error messages */ | ||
| const TRACE_USAGE_HINT = "sentry log list [<org>/[<project>/]]<trace-id>"; | ||
| const traceUsageHint = () => sentry`log list [<org>/[<project>/]]<trace-id>`; |
There was a problem hiding this comment.
traceUsageHint under npx/mcp double-prefixes ambiguous-project errors
Building this hint with sentry makes it start with npx -y … or sentry-mcp, but commandNameFromUsageHint still strips only /^sentry\s+/, so ambiguous project-search errors rebuild suggestions like npx -y sentry npx -y sentry log list …. Strip via getInvocationCommand() (or pass a bare command name) before re-wrapping with sentry.
Evidence
traceUsageHintis now() => sentry\log list …`and is passed intoresolveTraceOrg` (list.ts) as a full invocation string.commandNameFromUsageHintonly strips/^sentry\s+/(trace-target.ts), so under npx/mcp it returns e.g.npx -y sentry log listinstead oflog list.resolveTraceOrg→ project-search →resolveOrgOnlyTarget/resolveProjectBoundTargetre-wraps that value withsentry\${commandName} …`` on ambiguous matches, producing a doubled program prefix.- Default identity
sentrystill works; the failure needs npx (npm_command=exec/_npx) orsetAppIdentity({ name: "sentry-mcp", … })plus an ambiguous bare project + trace id.
Also found at 1 additional location
packages/cli/src/commands/span/list.ts:186-186
Identified by Warden · code-review · D89-TDK
There was a problem hiding this comment.
Confirmed and fixed in #2086. The trace resolvers now take the bare command name (log list, span list, span view, trace logs, trace view) and no longer derive it from the hint. commandNameFromUsageHint is deleted. A regression test checks that under sentry-mcp the program name appears only once.
) Fixes a regression from #2085 that [Warden reported](#2085 (comment)). It also removes the last runtime parsing of a leading `sentry ` on this path. ## Summary #2085 made the trace usage hints start with the running program (`npx -y sentry …`, `sentry-mcp …`). `commandNameFromUsageHint` in `lib/trace-target.ts` still stripped only a leading `sentry `. Under npx or `sentry-mcp`, it returned `npx -y sentry log list`, and the `sentry` tag then added the program name a second time. Ambiguous-project errors suggested `npx -y sentry npx -y sentry log list org-a/frontend/…`. ## What changed - `resolveTraceOrg`, `resolveTraceOrgProject` and `resolveTraceOrgOptionalProject` take a `commandName` argument. Each command passes its own bare name: `log list`, `span list`, `span view`, `trace logs` or `trace view`. - `commandNameFromUsageHint` and its two regexes are deleted. Nothing derives a command name from hint text anymore. - The usage hint is still used only as the hint that gets displayed. ## How it was tested - New regression test in `test/lib/trace-target.test.ts`. Under a `sentry-mcp` identity, an ambiguous bare project in all three resolvers suggests `sentry-mcp <command> org-a/frontend` and never `sentry-mcp sentry-mcp`. I checked that the test fails when the old hint parsing is put back. - `tsc --noEmit` (packages/cli): passes. - Root `pnpm run lint`: 0 warnings, 0 errors. - `node --test scripts/cli-lint-rules.test.mjs`: 116 pass. - `pnpm --filter sentry run check:doc-examples`: 1649 examples match. - `vitest` on `test/lib/trace-target.test.ts` and `test/commands/{log,span,trace}`: 374 pass, 2 skipped. Refs #2085 Refs #2050 <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3Ac4d53d732a16cb4ca0834ed4529d411d --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3Ac4d53d732a16cb4ca0834ed4529d411d) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3Ac4d53d732a16cb4ca0834ed4529d411d/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com>
) `docs.examples` steps no longer repeat `sentry `. `exampleForRunningProgram` is deleted from `src/lib/command.ts`. It was the last runtime match on command text: `--help` used to swap a leading `sentry ` for the running program. ## Schema ```ts type ExampleStep = string | { prefix?: string; args: string; suffix?: string }; type CommandExample = { description: string; run: ExampleStep | readonly ExampleStep[] }; ``` - The field is renamed `command` → `run`, so tsc flags any call site that was missed. This is a hard cutover with no shim. - A string step holds only the words after the program name, e.g. `run: "issue list my-org/frontend"`. - `exampleSteps(example, program)` renders `prefix + program + " " + args + suffix`. Each renderer picks the program: - `--help` (`prepareNativeDocs`) uses `getInvocationCommand()`, so it shows `sentry-mcp …` or `npx -y @sentry/mcp-server …`. - `introspect.ts`, which feeds the generated command docs, skill references and `check:fragments`, uses `"sentry"`. So do `check:doc-examples` and `generate-docs-sections`. ## Steps that aren't a bare `sentry` command Of 711 steps in 697 examples, 4 wrap the invocation in other shell text. These became `{ prefix, args, suffix }`: | File | Step | | --- | --- | | `commands/cli/fix.ts` | `sudo sentry cli fix` → `{ prefix: "sudo ", args: "cli fix" }` | | `commands/cli/completion.ts` | `eval "$(sentry cli completion bash)"` → `{ prefix: 'eval "$(', args: "cli completion bash", suffix: ')"' }` | | `commands/wasm-split.ts` | `BUILD_ID=$(sentry wasm-split app.wasm)` → `{ prefix: "BUILD_ID=$(", args: "wasm-split app.wasm", suffix: ")" }` | | `commands/monitor/run.ts` | `SENTRY_DSN=… sentry monitor run nightly-job -- python manage.py cron` → `{ prefix: "SENTRY_DSN=… ", args: "monitor run …" }` | No step is a pipe, an `npx …` line, another tool, or a shell line with no `sentry` invocation in it. A literal `{ shell }` form would have had no users, so I left it out. Trailing redirections such as `cli completion zsh > ~/.local/share/zsh/site-functions/_sentry` follow the command, so they stay in the plain string. Behavior change: under `sentry-mcp` or npx, `--help` now names the running program inside these 4 wrapped steps too. The old swap only matched a leading `sentry `, so it skipped them. ## Conversion and guard - An AST codemod (TypeScript compiler API) found each `examples: [{ description, command }]` entry. It renamed the key to `run`, stripped the leading `sentry ` from every string step (keeping the original quotes and escapes), and converted the 4 wrapped steps above. It found no non-literal steps and no unmatched ones. - `sentry-cli/no-hardcoded-sentry-command` now rejects an example step that starts with `sentry `: a `run` string, an element of a `run` array, or a step's `args`. RuleTester cases cover each form. - `check:doc-examples` still checks every rendered step against the route tree (1649 examples in 669 files). A doubled `sentry sentry …` would fail there as an unknown command. ## Verification - **Byte-identical output.** I ran `pnpm run generate:docs` on `main` and on this branch, then diffed recursively: `apps/cli-docs/src/content` (command pages, `index.md`, `configuration.md`, docs sections), `plugins/sentry-cli` (SKILL.md plus 42 references), `.well-known/skills/index.json`, `src/generated/` (including `skill-content.ts`) and `sdk.generated.*`. There were no differences. - New `test/lib/command-examples.test.ts`: `--help` renders `sentry-mcp …`, `sudo sentry-mcp cli fix` and `npx -y @sentry/mcp-server …` under those identities, and `exampleSteps(e, "sentry")` gives the docs form. - `pnpm run tsc` and `pnpm run lint` (root) pass. - Rule tests: `node --test scripts/cli-lint-rules.test.mjs` passes 118 of 118. - `check:doc-examples`, `check:fragments` and `check:docs-sections` pass. - `pnpm --filter sentry run test`: 12933 passed. 5 failed, all known sandbox permission failures: completions EACCES/EPERM (2), db/resilient readonly, the dsn code-scanner unreadable file, and the upgrade lock. Refs #2085 Refs #2050 <!-- junior-request-attribution:start --> via @BYK. <!-- junior-request-attribution:end --> <!-- junior-session-footer:start --> <!-- junior-conversation-id:agent%3Ac4d53d732a16cb4ca0834ed4529d411d --> -- [View Session](https://junior-prod.sentry.dev/conversations/agent%3Ac4d53d732a16cb4ca0834ed4529d411d) [[Sentry]](https://sentry.sentry.io/explore/conversations/agent%3Ac4d53d732a16cb4ca0834ed4529d411d/?project=4510944073809921) <!-- junior-session-footer:end --> Co-authored-by: sentry-junior[bot] <264270552+sentry-junior[bot]@users.noreply.github.com> Co-authored-by: BYK <126780+BYK@users.noreply.github.com>
Follow-up to #2071, as BYK asked: remove
asInvocationCommand(command)and the runtimestartsWith("sentry ")rewriting of hints.What changed
asInvocationCommandis deleted. All 51 call sites are gone.ContextError,ResolutionError,buildValidationMessageandorg-listnow print the text they receive, with no rewriting.LazyCommandthunks. Any command kept in a constant or table is now() => sentry`…`and gets built when it is shown. That covers 98 module-levelsentry …literals:USAGE_HINTs,commandPrefix, suggestion tables,SLUG_REDIRECT_COMMAND, and the issue, release, replay and dashboard argument builders. Every one of them was static text, so none needed runtime data.sentry`…`tag inserts interpolated values as they are. A nested tag result already starts with the program name, and plain text that happens to start with "sentry " is no longer rewritten. That closes the gap where user input could get rewritten.sentry-cli/no-hardcoded-sentry-command):'sentry issue list …'constant is now flagged.asInvocationCommandhandling is removed.issueTargetdefaultsentry`issue`used to be evaluated at module load, beforesetAppIdentity. The newtest/lib/app-identity-import.test.tsimportsapp.jsandmcp-server-app.jsand asserts that no hint gets built at import. I checked that it fails when that eager evaluation is put back.errors.tsJSDoc, the RuleTester cases and the app-identity tests are updated.Design choice: thunks instead of a lazy object
I first tried having the tag return a lazy
{ toString() }command value. That produced 174 tsc errors across about 50 files, because everystringslot would have to accept the new type. A value like that would also serialize as{}in JSON output. Thunks are type-checked, make the moment of evaluation explicit, and keep the literal tag text visible tocheck:doc-examples. Its tag scan still covers the text inside the thunks: 1649 examples in 669 files match the CLI.One remaining prefix swap (open question)
The
--helprendering ofdocs.examplesstill replaces a leadingsentrywith the running program, in a privateexampleForRunningPrograminlib/command.ts. That data covers about 480 examples, and the generated docs and skill references share it, so they need the literalsentryname. The static alternative is to dropsentryfrom thedocs.examplesdata and let each renderer add its own program name. That is a schema change across every command, so I left it out of this PR. I can do it as a follow-up if you want.How it was tested
pnpm run tsc: all packages pass.pnpm run lint: 0 warnings, 0 errors (includes ast-grep and the deps check).node --test scripts/cli-lint-rules.test.mjs: 116 pass.pnpm --filter sentry run check:doc-examples: 1649 examples match.pnpm --filter sentry run test: only the 5 known sandbox permission failures remain (completions EACCES/EPERM, resilient readonly, code-scanner unreadable files, upgrade lock). The 35 test failures from string fixtures were fixed by switching those fixtures to thunks, and those 4 files pass again (108 tests).Refs #2071
Refs #2050
via @BYK.
--
View Session [Sentry]