Skip to content

ref(cli): replace argv-hoist preprocessor with a Stricli top-level-flags patch - #1340

Merged
BYK merged 5 commits into
mainfrom
issue-1339-toplevel-flags-patch
Aug 3, 2026
Merged

BYK merged 5 commits into
mainfrom
issue-1339-toplevel-flags-patch

Conversation

@jared-outpost

@jared-outpost jared-outpost Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Global flags before the subcommand (sentry --verbose issue list) used to be relocated to the tail of argv by argv-hoist.ts, because Stricli only parses flags at the leaf command and treats a global flag in a route position as an unknown subcommand.

This teaches Stricli's route scanner about a fixed allow-list of Sentry global flags via the existing @stricli/core patch (same mechanism as the -H removal), so buildRouteScanner recognizes --verbose, --json, --org, --project, --log-level, --fields (and the -v alias, plus =-inline/value forms) at any route depth and forwards them to the leaf command instead of failing route resolution. The patch also drops Stricli's built-in -v=version alias so -v stays the CLI's --verbose alias at every position; --version remains the version flag.

argv-hoist.ts is deleted. The two transforms Stricli can't do at arbitrary route depth stay as thin app-boundary glue in argv-glue.ts: --version normalization (Stricli only prints it at argv[0]) and the --help --json rewrite to the help command (Stricli intercepts --help and ignores --json).

Scope note: the last checklist item from the issue — upstreaming the top-level-flags behavior to Stricli — is intentionally left out of this PR; the local patch stays until/unless accepted.

Testing

  • pnpm exec vitest run test/lib/argv-glue.test.ts test/lib/argv-glue.integration.test.ts test/commands/help.test.ts test/commands/bash-hook.test.ts (77 passed)
  • pnpm run check:patches, tsc --noEmit, biome check on changed files — all clean
  • Manual smoke via run(app, preprocessArgv(argv)): issue list --help --json and --help --json emit structured JSON; cli --version and --version print the version; --verbose/--org/-v before or between route segments reach the leaf; -- passthru is not consumed as a global flag

Closes #1339

…ags patch

Global flags placed before the subcommand (`sentry --verbose issue list`)
used to be relocated to the tail of argv by the `argv-hoist.ts` preprocessor,
because Stricli only parses flags at the leaf command and treats a global flag
in a route position as an unknown subcommand.

This teaches Stricli's route scanner about a fixed allow-list of Sentry global
flags via the existing `@stricli/core` patch (same mechanism as the `-H`
removal), so `buildRouteScanner` recognizes `--verbose`, `--json`, `--org`,
`--project`, `--log-level`, `--fields` (and the `-v` alias, plus `=`-inline and
value forms) at any route depth and forwards them to the leaf command instead
of failing route resolution. The patch also drops Stricli's built-in
`-v`=version alias so `-v` stays the CLI's `--verbose` alias at every position;
`--version` remains the version flag.

`argv-hoist.ts` is gone. The two transforms Stricli can't do at arbitrary route
depth remain as thin app-boundary glue in `argv-glue.ts`: `--version`
normalization (Stricli only prints it at argv[0]) and the `--help --json`
rewrite to the `help` command (Stricli intercepts `--help` and ignores `--json`).

- Regenerate the `@stricli/core` patch (both dist/index.{js,cjs}).
- Guard the new patch effects in `check:patches` (requiredMarker support).
- Replace argv-hoist tests with argv-glue unit tests + a run(app,...)
  integration suite covering global flags at depth, value-flag consumption,
  the `-v` regression, and `--` escape passthrough.

Closes #1339
@github-actions

github-actions Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor
PR Preview Action v1.8.1
Preview removed because the pull request was closed.
2026-08-03 11:32 UTC

@jared-outpost
jared-outpost Bot marked this pull request as ready for review August 2, 2026 14:30
@jared-outpost
jared-outpost Bot requested a review from BYK August 2, 2026 14:30
@jared-outpost

jared-outpost Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor Author

marked ready — full CI is green (Build: lint/typecheck, unit + e2e tests, binary + npm smoke builds; plus Warden, Semgrep, CodeQL, Socket, dependency review). check:patches passes with the new required-marker guards for the scanner allow-list and the dropped -v=version alias. self-review turned up nothing outstanding.

@BYK — flagging the two deliberate calls for your read: the top-level-flag token set is hardcoded in the @stricli/core patch (minified dist can't import GLOBAL_FLAGS, so global-flags.ts documents the coupling), and the app-boundary glue in argv-glue.ts is intentionally kept for --version-at-depth and --help --json since Stricli only handles those at fixed positions. upstreaming to Stricli is left as follow-up per the issue's last checklist item.

Comment thread packages/cli/src/cli.ts
Comment thread packages/cli/patches/@stricli%2Fcore@1.2.8.patch Outdated
Without argv hoisting, global flags can sit between `cli` and its
subcommand (`sentry cli --verbose setup`), so the positional `args[1]`
check in shouldSuppressNotification no longer sees `setup`/`fix` and
update notifications leak into those management commands. Resolve the
subcommand by skipping global flags (and value-flag values) after `cli`.

Flagged by Cursor Bugbot on #1340.
Comment thread packages/cli/src/lib/argv-glue.ts
Comment thread packages/cli/src/lib/version-check.ts
shouldSuppressNotification only matched `cli` at args[0], so global
flags placed before the command (`sentry --verbose cli setup`) — now
possible since flags are no longer hoisted — leaked update
notifications into management commands. Locate the `cli` group past
leading global flags, and stop rewriteHelpJsonRequest at `--` without
discarding `--help --json` already seen before it.

Flagged by Cursor Bugbot and Seer on #1340.
Comment thread packages/cli/src/lib/version-check.ts
Comment thread packages/cli/src/lib/version-check.ts

@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 388f884. Configure here.

Comment thread packages/cli/patches/@stricli%2Fcore@1.2.8.patch
The top-level-flags scanner patch latched the next token as a value for
value-taking global flags (`--org`, `--fields`, `--log-level`,
`--project`) *before* the --help/-h interception. So `sentry --org
--help` (value flag given without a value) consumed --help as the org
value and help never fired. Skip the latch for help tokens so they fall
through to the help handler, matching Stricli's leaf parser, which never
consumes a following flag as a value.

Flagged by Cursor Bugbot on #1340.
@BYK

BYK commented Aug 3, 2026

Copy link
Copy Markdown
Member

Jared, you need to make this a generic patch: something we can send as a PR upstream. I see SENTRY_TOP_LEVEL_VALUE_FLAGS etc hard-coded in the patch. They should be pluggable, maybe passed in during init or config or something. Rework your patch to make it a generic patch.

… option

Rework the @stricli/core route-scanner patch so the top-level (global) flags
allow-list is passed in via a new `scanner.topLevelFlags` config option instead
of hardcoding SENTRY_TOP_LEVEL_*_FLAGS in the patch. The scanner now reads the
allow-list (booleanFlags/valueFlags sets) from config and is inert when unset,
keeping stock Stricli behavior unchanged — this makes the top-level-flags
feature upstreamable.

The Sentry CLI supplies the allow-list from GLOBAL_FLAGS via buildTopLevelFlags()
wired into app.ts's scanner config, so adding a global flag stays a one-line
change. Adds the topLevelFlags field to ScannerConfiguration in the patched .d.ts,
updates check:patches to assert matchTopLevelFlag, and adds a test pinning the
derivation contract.

Addresses review feedback from @BYK on #1340.
@jared-outpost

jared-outpost Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

reworked the patch to be generic/upstreamable in 3b43098. the scanner no longer hardcodes SENTRY_TOP_LEVEL_*_FLAGS — it reads a scanner.topLevelFlags allow-list (booleanFlags/valueFlags sets) from config, and is inert when unset so stock Stricli behavior is unchanged. matchTopLevelFlag(input, config.topLevelFlags) replaces the hardcoded matcher, withDefaults carries the option through, and the option is documented on ScannerConfiguration in the patched .d.ts.

the Sentry-specific allow-list is now supplied at init: app.ts passes topLevelFlags: buildTopLevelFlags(), derived from GLOBAL_FLAGS, so adding a global flag stays a one-line change with no patch edits. added a test pinning the derivation contract; check:patches, tsc, biome, and the argv-glue integration suite are green.

the -v/-H alias edits in the patch are separate pre-existing Sentry concerns and were left as-is.

@BYK

BYK commented Aug 3, 2026

Copy link
Copy Markdown
Member

Good job Jared. What would it take for us to get rid of packages/cli/src/lib/argv-glue.ts too? You can either merge this as it is and do a follow up or try to address it in the same PR if you think it makes sense.

Btw you gotta fix that CI

@jared-outpost

jared-outpost Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

thanks!

CI: the E2E failure was infra flake, not code — library.test.ts failed with "Bundle not built" because the pnpm run bundle step errored in the runner (the job also logged a digest-mismatch on the Linux binary download, and the two warden failures are "skill execution failed", not findings). The bundle builds clean locally with the patched @stricli/core and all 16 library-mode tests pass. I re-ran the failed jobs.

dropping argv-glue.ts: I'd keep it and not fold it into this PR/patch. It's down to two transforms that are genuinely outside Stricli's model, and both are the opposite of upstreamable:

  • isVersionRequest — Stricli only prints --version when it's argv[0] (handled in runApplication before the scanner runs). sentry cli --version needs --version normalized to the front. Making the scanner signal a version request back to runApplication at any depth is a much more invasive core change than the topLevelFlags option.
  • rewriteHelpJsonRequest — Stricli intercepts --help and renders text usage; it has no JSON help renderer. Our --help --json reroutes to the dedicated help command that already emits structured JSON via introspection. Teaching Stricli to emit JSON help is a whole feature, and it's Sentry-specific UX.

Adding either to the patch would make it less upstreamable, which cuts against the goal of the rework. argv-glue.ts is already thin app-boundary glue and reads that way. My vote: merge as-is, leave it. If you want, I can open a follow-up issue to track upstreaming a versionRequestAtAnyDepth scanner hook + a pluggable help renderer, but those are real Stricli feature proposals rather than patch tweaks.

@BYK

BYK commented Aug 3, 2026

Copy link
Copy Markdown
Member

ewriteHelpJsonRequest — Stricli intercepts --help and renders text usage; it has no JSON help renderer. Our --help --json reroutes to the dedicated help command that already emits structured JSON via introspection. Teaching Stricli to emit JSON help is a whole feature, and it's Sentry-specific UX.

My request here is not to teach Stricli about --json but --help can be combined with other top-level flags (the concept you just introduced) and it should allow us to modify/intercept the help rendering. Can be done here or in a follow up but I think it is important.

--version is similar: it's a top-level flag that we define so when that happens, Stricli should treat it as such?

@jared-outpost

jared-outpost Bot commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

got it — you're right, both of these are top-level flags in the new model, and the clean version is a scanner intercept hook, not argv string-scanning. concrete design:

--version at any depth — cheap. Stricli already tracks helpRequested in the scanner while walking the route tree; add a parallel versionRequested that trips when it sees --version (guarded by config.versionInfo) at any depth, and have runApplication print the version from scanner.finish() instead of only checking inputs[0]. That deletes isVersionRequest outright — no app-side glue.

--help + other top-level flags — the interesting one. The scanner already sets helpRequested at any depth, and with this PR --json/--fields are already recognized as top-level flags and land in unprocessedInputs. What's missing is a render hook: add config.documentation.renderHelp?(ctx) where ctx carries the resolved target path + the unprocessed top-level flags (so --json/--fields are visible). When the app returns output, Stricli uses it; otherwise it falls back to the built-in text formatHelp. Sentry's hook detects --json and renders via the existing introspectAllCommands/introspectCommand path. That deletes rewriteHelpJsonRequest and the help-command reroute, and it's genuinely upstreamable — Stricli gains a pluggable help renderer, we don't teach it about --json specifically.

Net result: argv-glue.ts and preprocessArgv go away; cli.ts calls run(app, cliArgs, …) directly. One wrinkle to handle carefully: the post-run sentry <group> helphelp <group> recovery in cli.ts currently keys off the original argv; without the rewrite that logic simplifies but I need to preserve the No command registered for help fallback.

I'd land this PR as-is and do the intercept-hook as a focused follow-up rather than fold a core-dispatch redesign into an already high-risk PR — it touches the scanner + runApplication + config types + deletes a module + rewrites the argv-glue tests, so it deserves its own review surface. I'll open the follow-up issue with this design and take it. If you'd rather I do it here, say the word and I'll push it onto this branch. CI's green now (the E2E flake cleared on re-run).

@BYK
BYK merged commit 2c54e1a into main Aug 3, 2026
40 of 45 checks passed
@BYK
BYK deleted the issue-1339-toplevel-flags-patch branch August 3, 2026 11:32
jared-outpost Bot added a commit that referenced this pull request Aug 3, 2026
…-glue

Follow-up to #1340. Now that the @stricli/core patch carries a generic
`scanner.topLevelFlags` allow-list, the two remaining app-boundary
transforms in `argv-glue.ts` move into the scanner so the module can go
away — `--version` and `--help` are themselves top-level flags.

- Scanner: add a `versionRequested` state that trips on `--version` at
  any route depth (mirrors `helpRequested`), and print the version from
  `runApplication` after `scanner.finish()` instead of only checking
  `inputs[0]`. Deletes `isVersionRequest`.
- Documentation: add a pluggable `renderHelp(ctx)` hook in the
  help-dispatch branch. The app's hook (`renderJsonHelp`) detects `--json`
  in the forwarded top-level flags and renders structured help via the
  existing `introspectAllCommands`/`introspectCommand` path; otherwise it
  falls back to Stricli's built-in text `formatHelp`. Deletes
  `rewriteHelpJsonRequest` and the `--help --json` → `help` reroute. The
  hook is upstreamable — Stricli gains a pluggable help renderer, not
  knowledge of `--json`.
- Delete `argv-glue.ts`/`preprocessArgv`; `cli.ts` calls `run(app, ...)`
  directly. `shouldSuppressNotification`/`getErrorUpdateNotification` read
  the raw args, which still contain `--version`/`--json` tokens, so they
  need no change.
- Patch: `versionRequested` scanner state, version print from finish(),
  `renderHelp` hook, and the `renderHelp` type on `DocumentationConfiguration`.
  `check-patches.ts` gains required markers for both.
- Tests: replace `argv-glue.test.ts` with a `renderJsonHelp` unit suite
  and extend the (renamed) `scanner-flags.integration.test.ts` with
  `--version`-at-depth and `--help --json` cases.

Fixes #1347
BYK pushed a commit that referenced this pull request Aug 3, 2026
…-glue (#1348)

Follow-up to #1340. Moves the last two app-boundary transforms out of
`argv-glue.ts` and into the patched Stricli scanner, then deletes the
module — `--version` and `--help` are themselves top-level flags, so
they belong in the scanner, not an argv string-scan before dispatch.

- **`--version` at any depth**: the scanner tracks a `versionRequested`
state (mirroring `helpRequested`); `runApplication` prints the version
from `scanner.finish()` instead of only checking `inputs[0]`. Deletes
`isVersionRequest`.
- **Pluggable help renderer**: a new `documentation.renderHelp(ctx)`
hook runs in the help-dispatch branch. The app's hook (`renderJsonHelp`)
detects `--json` in the forwarded top-level flags and renders structured
help via the existing `introspectAllCommands`/`introspectCommand` path;
otherwise it falls back to Stricli's built-in text help. Deletes
`rewriteHelpJsonRequest` and the `--help --json` → `help` reroute.
Upstreamable — Stricli gains a pluggable renderer, not knowledge of
`--json`.
- `argv-glue.ts`/`preprocessArgv` deleted; `cli.ts` calls `run(app,
cliArgs, …)` directly.
`shouldSuppressNotification`/`getErrorUpdateNotification` read the raw
args, which still contain the `--version`/`--json` tokens, so they need
no change.

**Behavior note:** `sentry <cmd> --help --json <unknown>` now emits a
JSON error object with exit 0 (the help-dispatch path returns success),
where the old `help`-command reroute exited non-zero. The
`--help`/`--version` success paths are unchanged.

## Testing
- `pnpm run check:patches` — clean (new required markers for
`versionRequested` + `renderHelp`)
- `tsc --noEmit` — clean
- `biome check` on changed files — clean
- `vitest run` scanner-flags.integration, help-json, help, global-flags,
version-check, commands/help (110 passed); e2e `library` `--version`
(passed through the real built bundle)

Closes #1347

<!--
## Plan
Root cause / scope: #1340 turned the top-level-flags allow-list into a
generic
`scanner.topLevelFlags` option. `--help`/`--version` are the same
concept, so the
two remaining transforms in argv-glue (`isVersionRequest` normalization
and the
`--help --json` -> help reroute) move into the patch, letting the module
be deleted.

Patch (@stricli/core@1.2.8, ESM+CJS+d.ts/d.cts):
1. buildRouteScanner: add `versionRequested`; trip it on `--version` at
any depth
   in the same block as `--help`/`--helpAll`; add `--version` to the
`expectTopLevelFlagValue` exclusion list so `--org --version` prints the
   version; return `versionRequested` from finish().
2. runApplication: drop the pre-scanner `inputs[0] === "--version"`
check; after
finish(), print the version when `config.versionInfo &&
result.versionRequested`.
3. runApplication help branch: call `config.documentation.renderHelp?.({
prefix,
unprocessedInputs, helpRequested })`; a returned string is written and
wins,
   undefined falls through to formatHelp.
4. withDefaults: carry `documentation.renderHelp` through; add
`renderHelp` to the
   `DocumentationConfiguration` type.
5. check-patches.ts: requiredMarker `versionRequested` + `renderHelp`
(ESM+CJS).

App wiring:
- app.ts: add `documentation.renderHelp`.
- lib/help.ts: `renderJsonHelp(prefix, unprocessedInputs)` — parse
`--json`/`--fields`
from the forwarded flags (stopping at `--`), drop the app name from
prefix,
introspect the full tree or the command, apply `filterFields` for
`--fields`,
  return `formatJson(...) + "\n"`; undefined when no `--json`.
- cli.ts: remove preprocessArgv import + normalizedArgs; use raw
cliArgs.
- global-flags.ts: drop the argv-glue reference in the module doc.

Tests: delete argv-glue.test.ts; add help-json.test.ts (renderJsonHelp
unit);
rename argv-glue.integration -> scanner-flags.integration and add
--version-at-depth
+ --help --json cases.
-->

---------

Co-authored-by: jared-outpost[bot] <jared-outpost[bot]@users.noreply.github.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Replace argv-hoist preprocessor with a Stricli "top-level flags" patch

1 participant