Skip to content

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

Open
sentry-junior[bot] wants to merge 1 commit into
mainfrom
perf/cli-complete-fast-entry
Open

sentry-junior[bot] wants to merge 1 commit 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 30, 2026 9:57pm 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.

"-e",
`process.on("exit", () => process.stderr.write(JSON.stringify(Object.keys(require.cache))));
process.argv.splice(1, 0, ${JSON.stringify(BUNDLE_BIN_PATH)});
require(process.argv[1]);`,

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.

E2E test misses completion entry

Medium Severity

The new e2e guard inserts the bin path in front of -e instead of replacing it, so process.argv[2] is -e rather than __complete. The wrapper therefore loads index.cjs and never takes the complete.cjs fast path, so the assertions on completion output and loaded modules cannot hold.

Fix in Cursor Fix in Web

Reviewed by Cursor Bugbot for commit f410427. Configure here.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a bug: Node leaves -e and its script out of process.argv, so with node -e <script> __complete … the array is [node, "__complete", …]. After the splice it is [node, bin.cjs, "__complete", …] and argv[2] is "__complete". I checked this locally on Node 24, and the test passes against a fresh pnpm run bundle. Its require.cache shows bin.cjs and complete.cjs only.

Comment thread packages/cli/src/bin.ts
Comment on lines +54 to +60
if (process.argv[2] === "__complete") {
import("./lib/complete.js")
.then(({ runCompletion }) => runCompletion(process.argv.slice(3)))
.catch(() => {
// Completions should never crash — silently return no results
process.exitCode = 0;
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Disabling telemetry bypasses SQLite schema repair

When SENTRY_CLI_NO_TELEMETRY=1—including on every __complete run—getDatabase() returns raw SQLite instead of the proxy that repairs schema errors and handles readonly writes. A completion query against a stale schema can therefore fail and silently produce no suggestions; keep database resilience independent of telemetry loading.

Evidence
  • runCompletion() sets SENTRY_CLI_NO_TELEMETRY to "1"; lazyTelemetry() then returns undefined, and getDatabase() falls back to rawDb.
  • createTracedDatabase() and createTracedStatement() are the only wrappers that call tryRepairAndRetry() for schema errors and handle readonly write errors. schema.ts documents repair for missing tables and columns.
  • getCachedOrganizations() queries columns including credential_identity; if one is missing, the raw query fails, and handleComplete() catches the failure and returns no completions rather than repairing and retrying.

Identified by Warden · code-review · NDZ-CMD

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not changing this in this PR: the behavior is unchanged from main, so the PR does not introduce it.

  • On main, runCompletion() in src/cli.ts already sets getEnv().SENTRY_CLI_NO_TELEMETRY = "1" before it imports complete.js.
  • On main, getDatabase() already sets db = rawDb when SENTRY_CLI_NO_TELEMETRY === "1", so __complete has always skipped the createTracedDatabase() repair proxy. Any user who sets SENTRY_CLI_NO_TELEMETRY=1 skips it too.
  • This PR only moves that branch into lazyTelemetry(). The result is the same: lazyTelemetry()?.createTracedDatabase(rawDb) ?? rawDb.
  • Stale schemas are still fixed when the database opens: getDatabase() runs initSchema() and runMigrations() on rawDb, and runMigrations() calls repairSchema() for databases from a newer CLI. Only the retry-on-query step is skipped. If completion fails, it returns no suggestions and does not crash.

Moving the repair and readonly handling out of telemetry.ts, so it works without telemetry, is a reasonable follow-up. It changes behavior for every no-telemetry run, though, so it belongs in its own PR, not this performance refactor.

This branch was previously deployed

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

0 participants