Skip to content

perf(cli): Give __complete its own light entry point - #1762

Merged
BYK merged 2 commits into
mainfrom
perf/cli-complete-fast-entry
Oct 31, 2026
Merged

BYK merged 2 commits into
mainfrom
perf/cli-complete-fast-entry

Conversation

@sentry-junior

@sentry-junior sentry-junior Bot commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

Shell completion runs sentry __complete on every TAB press, and since #1760 also for every flag name in bash and fish. Each call was paying for most of the CLI before it printed anything. This addresses the review thread on #1751 (complete.ts).

Where the time went

  • The npm bin required the whole 7.6 MB dist/index.cjs just to reach the completion branch in cli.ts.
  • The completion engine's eager import graph still reached @sentry/node, even with telemetry off. db/auth.ts and db/user.ts imported withDbSpan from telemetry.ts, the cache modules imported recordCacheHit, and logger.ts imported createConsolaReporter. That graph was 659 modules: Sentry SDK, OpenTelemetry, and the API client. The completers themselves cost under 10 ms.

What changed

  • src/bin.ts and dist/bin.cjs check for __complete first. They call runCompletion in src/lib/complete.ts without loading cli.ts. The npm package ships that path as a separate dist/complete.cjs (322 KB). The SEA binary takes it as a lazily evaluated chunk of its single bundle.
  • The database modules now get withDbSpan and recordCacheHit from db/index.ts. Like the existing tracing proxy there, these require telemetry lazily and skip it when telemetry is off, which runCompletion sets. initSentry now passes the Sentry reporter to attachSentryReporter, so logger.ts no longer imports the SDK.
  • The eager graph is down to 40 modules. Completion output and the protocol are unchanged: I diffed the output of several cases between the old and new npm and SEA builds.

Numbers. Median of 20 runs on Node 24 (Linux x64), against a seeded config DB:

bare node issue list "" issue list -- init ./ (directory)
npm before 26 ms 356 ms 359 ms 346 ms
npm after 26 ms 67 ms 72 ms 76 ms
SEA before 28 ms 145 ms 148 ms 145 ms
SEA after 27 ms 97 ms 97 ms 99 ms

In dev (tsx), the same calls went from about 650 ms to about 315 ms; most of what's left is tsx's own startup.

Guard. test/script/cli-startup.test.ts bundles lib/complete.ts without its deferred imports. It fails on any package outside COMPLETION_PACKAGES, naming the importer, or when there are more than 50 source modules. It also now covers bin.ts. The npm e2e suite checks that __complete loads complete.cjs and never index.cjs. The "Shell completion" section of AGENTS.md describes the rule.

What's left. consola is about 15 ms of the remaining npm time, and Stricli (through errors.ts) about 3 ms. In the SEA binary, the rest is mostly the cost of loading the single 7.6 MB embedded script. Each of these would need a larger change (a lazy logger, a second SEA script), so they are left for follow-ups.

via BYK.

--

View Session in Sentry

Shell completion runs on every TAB press, but `sentry __complete` paid
for most of the CLI before printing anything. The npm bin required the
whole 7.6 MB `dist/index.cjs` (~340 ms per call), and the completion
engine's static imports reached `@sentry/node` anyway: `db/auth.ts` and
the cache modules imported telemetry, and `logger.ts` imported
`createConsolaReporter`. Its eager graph was 659 modules.

`src/bin.ts` and `dist/bin.cjs` now check for `__complete` first and
call `runCompletion` in `lib/complete.ts` without loading `cli.ts`; the
npm package ships it as a separate `dist/complete.cjs` (322 KB). The
database modules get `withDbSpan` and `recordCacheHit` from
`db/index.ts`, which requires telemetry lazily like the existing
tracing proxy and skips it with telemetry off, and `initSentry` hands
the logger its Sentry reporter. The eager graph is now 40 modules.

Median on Node 24 (`issue list ""`): npm 356 -> 67 ms, SEA binary
145 -> 97 ms, bare node 26 ms. Completion output is unchanged.

`test/script/cli-startup.test.ts` bundles `lib/complete.ts` and fails on
any package outside an allowlist or more than 50 source modules, and
the npm e2e test checks `__complete` never loads `index.cjs`.

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

vercel Bot commented Oct 30, 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 31, 2026 2:05pm UTC

Request Review

@github-actions github-actions Bot added the risk: medium PR risk score: medium label Oct 30, 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.

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 f410427. Configure here.

Comment thread packages/cli/test/e2e/bundle.test.ts Resolved
Comment thread packages/cli/src/bin.ts Resolved
Comment thread packages/cli/script/bundle.ts Resolved
Comment thread packages/cli/src/lib/logger.ts Resolved
Comment thread packages/cli/src/bin.ts Resolved
BYK added a commit that referenced this pull request Oct 31, 2026
Part of #1761.

Zod 4's `core`, `classic`, and `mini` entry points all re-export
`locales/index.js`, which statically imports every locale. Nothing we
bundle calls `z.config(z.locales.xx())`; Zod sets its English default by
importing `locales/en.js` directly. A new esbuild plugin,
`script/lib/zod-locales-plugin.ts`, resolves Zod's locales index to a
module that exports only `en`. Error messages stay the same, and
`z.locales.en` still works.

The npm bundle (`script/bundle.ts`) and the standalone binary
(`script/build.ts`) now use the plugin. Both inline `@sentry/mcp-core`
for `sentry mcp`. The plugin lives in `script/lib/` so the
`@sentry/mcp-server` bundle from #1731 can import it next to
`require-alias-plugin.ts`.

| | before | after |
| --- | --- | --- |
| CLI `dist/index.cjs` | 7,869,567 B | 7,605,958 B (−263.6 KB) |
| Zod in the CLI bundle | 547,614 B | 284,024 B |
| `sentry --version` max RSS | 210.4 MB | 207.6 MB |
| `sentry mcp --version` max RSS | 178.0 MB | 175.0 MB |
| `sentry mcp` idle max RSS (initialize + tools/list) | 180.5 MB | 177.3
MB |
| `@sentry/mcp-server` bundle (#1731 branch, plugin applied locally) |
3,663,084 B | 3,399,473 B |
| `sentry-mcp --version` / idle max RSS (#1731) | 137 / 143 MB | 134 /
141 MB |

Byte counts come from the esbuild metafile. RSS is
`process.resourceUsage().maxRSS` from a `-r` preload, on Node 22, 3 runs
each.

The new test bundles real Zod, ESM and CJS, with and without the plugin.
It checks that the messages match and that `z.locales` has only `en`.

This touches `script/bundle.ts` in two lines, an import and one entry at
the top of the `plugins` array. #1762 adds code just below that array,
so the two may need a trivial rebase.

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

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:agent%3A97f516d754ebba138e04ff1efc71c075 -->

--

[View Session in
Sentry](https://sentry.sentry.io/explore/conversations/agent%3A97f516d754ebba138e04ff1efc71c075/?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>
…fine

Replace the esbuild resolve plugin that externalized the lazy
telemetry require with a SENTRY_CLI_COMPLETION_BUILD define. The
complete.cjs build sets it to true, so esbuild drops the require and
@sentry/node from that bundle. Source runs keep the
SENTRY_CLI_NO_TELEMETRY check.
@BYK
BYK merged commit 4b12058 into main Oct 31, 2026
26 checks passed
@BYK
BYK deleted the perf/cli-complete-fast-entry branch October 31, 2026 14:32
BYK added a commit that referenced this pull request Oct 31, 2026
This is groundwork for #1731. That PR builds `@sentry/mcp-server` from
the real CLI runner with a smaller route map (`{ mcp, auth, help }`,
`mcp` as the default command). Today that can't work: `lib/help.ts` and
`lib/completions.ts` import `routes` from `app.ts`, and
`lib/list-command.ts` loads it lazily with `require()`. Every command
imports `help.ts` through `buildCommand`, so bundling any one command
pulls in the whole CLI: every other command, `sentry init`, and Mastra.

`app.ts` now registers its route map with `setRoutes()` in the new
`lib/route-registry.ts`. `help`, `completions`, `list-command`, and the
`schema` command read it from there. `getRoutes()` throws a clear error
when nothing has been registered. `list-command` uses `findRoutes()`
instead, so a command's `func` called on its own, as unit tests do,
still runs without subcommand hints, as it did before. Another entry
point can register a smaller map.

Output doesn't change: `sentry --help`, `sentry help --json`, `sentry
cli completion bash`, `sentry schema issues --json`, and `sentry
projects list --help` are byte-identical before and after, from source
and from the npm bundle. The plural-alias hint (`sentry projects list` →
`Tip: "list" is a subcommand`) still works in the bundle.

Bundle size with an esbuild metafile (minified ESM, the same plugins as
`script/bundle.ts`):

| Entry | Before | After |
|---|---|---|
| `commands/auth/whoami.ts` | 1822 modules, 194 command files, 5.62 MB |
1156 modules, 1 command file, 2.89 MB |
| `lib/command.ts` | 1822 modules, 5.62 MB | 1107 modules, 2.55 MB |
| `commands/auth/index.ts` | 1822 modules, 5.62 MB | 1171 modules, 2.93
MB |
| `commands/mcp.ts` | 1822 modules, 5.62 MB | 1108 modules, 2.55 MB |

After the change, none of these include `app.ts` or `@mastra/*`.

Guard: `test/lib/route-registry.test.ts` bundles every module in
`src/lib/` and fails, naming the importer, if any of them reaches
`app.ts`. It follows lazy `_require()` loads too. `sdk-invoke.ts` is the
one exception: it's the library-mode runner, reached only from
`src/index.ts`, and it executes the whole app. The three
`interceptSubcommand` tests that were skipped because that `require()`
failed under vitest now run. `list-command` no longer needs the
`require-alias` rewrite, so it's removed from the filters in
`script/build.ts` and `script/bundle.ts`.

Not included, left for #1731 or later PRs: `cli.ts` still imports
`./app.js` for `run()` and unknown-command reporting. Making the
application injectable is the next step. I left `cli.ts` alone here to
stay clear of #1762. This branch merges cleanly with #1762. `whoami`
also still reaches `lib/init/ui/ink-ui.ts` through `resolve-target.ts`
(the Ink prompt UI, not the init wizard).

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

<!-- junior-session-footer:start -->
<!-- junior-conversation-id:agent%3A9214c867676f0f724d594cfe3d630133 -->

--

[View Session in
Sentry](https://sentry.sentry.io/explore/conversations/agent%3A9214c867676f0f724d594cfe3d630133/?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>
sentry-junior Bot added a commit that referenced this pull request Oct 31, 2026
Brings in #1762 (light `__complete` entry point). Completion now runs from
the bin entry points through `lib/complete.ts`'s `runCompletion`, so the
branch's `CliEntry.complete` hook and `startCli()`'s completion branch are
removed; `bin.ts` keeps main's dispatch and passes `SENTRY_CLI` to
`startCli()`. #1762's comment cleanup in `preloadProjectContext` is applied
to `lib/project-context.ts`, where this branch moved it.
sentry-junior Bot added a commit that referenced this pull request Oct 31, 2026
Adds `mcp-server.ts` to #1762's startup guard. The entry's eager graph is
14 source modules with no `@sentry/*` SDK import, so `--version` and a lone
`-v` return before `@sentry/node` or OpenTelemetry load; the guard keeps it
that way.

Co-Authored-By: BYK <126780+BYK@users.noreply.github.com>

This branch was previously deployed

1 inactive deployment
Preview — fd2a64a7 Deployed Oct 31, 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.

1 participant