feat: dynamic cache-backed shell completions with fuzzy matching#465
Merged
feat: dynamic cache-backed shell completions with fuzzy matching#465
Conversation
Contributor
Semver Impact of This PR🟡 Minor (new features) 📋 Changelog PreviewThis is how your changes will appear in the changelog. New Features ✨
Bug Fixes 🐛
Internal Changes 🔧
🤖 This preview updates automatically when you update the PR. |
Contributor
Codecov Results 📊✅ 126 passed | Total: 126 | Pass Rate: 100% | Execution Time: 0ms 📊 Comparison with Base Branch
✨ No test changes detected All tests are passing successfully. ✅ Patch coverage is 99.21%. Project has 1037 uncovered lines. Files with missing lines (3)
Coverage diff@@ Coverage Diff @@
## main #PR +/-##
==========================================
+ Coverage 95.71% 95.75% +0.04%
==========================================
Files 177 180 +3
Lines 24100 24417 +317
Branches 0 0 —
==========================================
+ Hits 23066 23380 +314
- Misses 1034 1037 +3
- Partials 0 0 —Generated by Codecov Action |
15f1077 to
34f1323
Compare
Add hybrid static + dynamic shell completion system that suggests cached org slugs, project slugs, and project aliases alongside existing command and subcommand names. Architecture: - Static: command/subcommand names, flag names, and enum values are embedded in the shell script for instant tab completion (0ms) - Dynamic: positional arg values (org slugs, project names, aliases) are completed by calling `sentry __complete` at runtime, which reads the SQLite cache with fuzzy matching (<50ms) New files: - src/lib/fuzzy.ts: Shared Levenshtein distance + fuzzyMatch() utility with tiered scoring (exact > prefix > contains > Levenshtein distance) - src/lib/complete.ts: Completion engine handling the __complete fast-path with context parsing, cache querying, and lazy project fetching Key changes: - src/bin.ts: Add __complete fast-path before any middleware/telemetry - src/lib/completions.ts: Extend extractCommandTree() with flag metadata; update bash/zsh/fish generators with flag completion and dynamic callback - src/lib/db/project-cache.ts: Add getAllCachedProjects() and getCachedProjectsForOrg() for completion queries - src/lib/platforms.ts: Use shared levenshtein() from fuzzy.ts Features: - Fuzzy matching for org/project names (typo-tolerant) - Flag name completion for all commands (static, instant) - Lazy project cache population on first tab-complete per org - Graceful degradation when DB/API unavailable - Tab-separated value\tdescription output for zsh/fish
34f1323 to
69c62ee
Compare
Address two review findings: 1. Seer: When user types 'senry/', fuzzy-resolve the org part to 'sentry' before querying projects. Previously the exact-match SQL query would find nothing for the typo'd org slug. 2. BugBot: In zsh, _arguments -C with '*::arg:->args' resets $words to only remaining positional args, stripping command/subcommand. Fixed by using $line[1] and $line[2] (set by _arguments -C) to pass the full command context to __complete.
…elpers Address BugBot round 2 findings: 1. Remove the heuristic that suppressed dynamic completions when the previous word starts with '--'. Boolean flags (--verbose, --json) don't consume the next word, so completions should still appear. The shell script already handles flag value completion statically. 2. Extract matchOrgSlugs() helper shared by completeOrgSlugs and completeOrgSlugsWithSlash, eliminating duplicated cache query, fuzzy match, and name lookup logic.
Address BugBot round 3 findings: 1. Bash: shellVarName() replaces [-. ] with _ at codegen time, but runtime lookup only replaced hyphens. Added __varname() bash helper that uses the same replacement pattern for consistent lookups. 2. Fish: commandline -opc includes the current token, so appending commandline -ct passed it twice. Split into preceding (commandline -opc) and current (commandline -ct) as separate variables.
Address BugBot round 4 findings: 1. SQL: Changed DISTINCT to GROUP BY project_slug so the same project cached with slightly different names (e.g., after a rename) only appears once, using the most recently cached name. 2. Fish: Added flag completions for standalone commands (e.g., api --method). Previously only grouped subcommands had flag completions.
Address BugBot round 5 findings: 1. Fish: Quote $current so empty partial (TAB after space) is passed as "" instead of being silently dropped by fish's empty-list expansion, which would misinterpret the last preceding word. 2. Bash: Add standalone fallback for enum value lookup. For standalone commands like 'sentry api --method', COMP_WORDS[2] is a flag not a subcommand, so the cmd_subcmd_flag_values lookup fails. Now falls back to cmd_flag_values.
SQLite GROUP BY with ORDER BY doesn't guarantee which row's non- aggregated columns are selected. Use MAX(cached_at) aggregate so SQLite deterministically picks project_name from the most recently cached row per project_slug.
When partial is just '/', orgPart is empty and fuzzyResolveOrg would match the first alphabetical org. Now returns empty early when orgPart is empty.
When _arguments -C resets $words to only positional args and there are none yet (user pressed TAB after 'sentry issue list '), the empty words array caused 'list' to be misinterpreted as the partial. Now conditionally appends an empty string only when words is empty.
BYK
commented
Mar 19, 2026
Shell completions were loading @sentry/bun (~280ms) via the import chain: complete.ts → db/index.ts → telemetry.ts → @sentry/bun. Completions only read cached SQLite data and never need telemetry. Changes: - db/index.ts: Remove top-level telemetry import. Add disableDbTracing() flag (same pattern as disableResponseCache/disableOrgCache). Lazy- require telemetry.ts inside getDatabase() only when tracing is enabled. - bin.ts: Move __complete dispatch before all heavy imports. Restructure as lightweight dispatcher: runCompletion() loads only db + complete modules; runCli() loads the full CLI with telemetry, Stricli, etc. Results (compiled binary): - Completion: 320ms → 190ms (40% faster) - Dev mode: 530ms → 60ms (89% faster) - Normal commands: unchanged (~510ms)
Code changes: - bin.ts: Static import for disableDbTracing (db/index.ts is lightweight now that telemetry import is lazy) - introspect.ts: Widen isRouteMap/isCommand to accept `unknown` so completions.ts can reuse them - completions.ts: Remove duplicate type guards (use introspect.ts); extractFlagEntries uses for..of; extractSubcommands uses filter.map; shellVarName uses whitelist (/[^a-zA-Z0-9_]/); bash/zsh standalone command completion at position 2; bash __varname uses same whitelist - complete.ts: Merge matchOrgSlugs into completeOrgSlugs with suffix param (no identity fn); eliminate [...nameMap.keys()] intermediate; export command sets for testing - completions.property.test.ts: Add drift-detection tests verifying hardcoded ORG_PROJECT_COMMANDS / ORG_ONLY_COMMANDS stay in sync with the actual route tree
BugBot: escapeDblQuote was missing $ escaping which is special in double-quoted bash/fish strings. Add \$ replacement before " to prevent variable expansion in completion descriptions.
Seer: While ;;\& works in both bash and zsh, ;| is the canonical zsh syntax for testing remaining case patterns.
…cing Replace the in-process tracingDisabled flag and disableDbTracing() export with the existing SENTRY_CLI_NO_TELEMETRY=1 env var. The __complete fast-path sets it before any imports, and db/index.ts checks it when deciding whether to lazy-require the tracing wrapper. Removes one export, one module-level variable, and one static import.
BYK
commented
Mar 19, 2026
- complete.ts: Build nameMap first, pass [...nameMap.keys()] to fuzzyMatch — eliminates the separate slugs array - completions.ts: Revert extractSubcommands to for-of loop (more efficient than filter.map for small arrays) - completions.ts: s/whitelist/allowlist in shellVarName JSDoc
BYK
commented
Mar 19, 2026
Fish triggers command substitution with () inside double quotes. Add \( and \) escaping to escapeDblQuote().
Same pattern as completeOrgSlugs — build nameMap first and derive keys from it instead of iterating projects twice.
The ;| terminator continues testing patterns, but args) doesn't match when state is 'subcommand'. Use args|subcommand) so the dynamic completion handler fires for standalone commands too.
Add .catch() handlers to runCompletion() and runCli() calls in the entry point dispatch. Completions silently return no results on error; CLI prints fatal error and sets exit code 1.
Spawns the actual CLI binary with __complete args and measures wall-clock latency. Catches regressions that would re-introduce heavy imports into the completion fast-path. Tests: - Completion is faster than a normal command (--version) - Completion exits under 500ms budget (pre-fix was ~530ms) - Completion exits cleanly with no stderr - Empty args handled gracefully
Remove the 'faster than normal command' comparison (not useful since we want both fast). Tighten the latency budget from 500ms to 100ms — the UX threshold for instant tab completion.
CI machines measured 141ms vs dev ~67ms. Use 200ms to accommodate CI variance while still catching regressions (pre-fix was ~530ms).
Add deferred telemetry for shell completions — zero SDK overhead during completion, metrics emitted opportunistically on next CLI run. How it works: 1. During __complete: After writing completions to stdout, queue one row to completion_telemetry_queue with timing + metadata. Pure SQLite, no Sentry SDK. ~1ms overhead. 2. During normal CLI runs: Inside withTelemetry(), drain the queue and emit each entry as Sentry.metrics.distribution() with command_path attribute. New files: - src/lib/db/completion-telemetry.ts: queueCompletionTelemetry() and drainCompletionTelemetry() for write/read/delete operations Schema: version 10 → 11 (new completion_telemetry_queue table) Also updates stale @sentry/bun references to @sentry/node-core after the telemetry refactor merge.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Replace separate SELECT + DELETE in drainCompletionTelemetry() with a single DELETE ... RETURNING statement. Prevents concurrent __complete processes from losing rows inserted between the two steps.
4 tasks
betegon
added a commit
that referenced
this pull request
Mar 20, 2026
## Summary Shell completions showed org slugs but not projects after the slash. The completion PR (#465) described "lazy project cache population" but only DSN resolution actually wrote to the `project_cache` table. Commands like `project list`, `issue list`, etc. fetched projects but never cached them for completions. Adds `cacheProjectsForOrg()` at the API layer (in `listProjects()`), mirroring how `listOrganizations()` calls `setOrgRegions()`. Every command that lists projects now automatically seeds the completion cache. ## Changes - `src/lib/db/project-cache.ts` — Add `cacheProjectsForOrg()` batch function using transactional upsert (same pattern as `setOrgRegions()`) - `src/lib/api/projects.ts` — Call cache after `listProjects()` returns (best-effort, wrapped in try/catch) - `test/lib/db/project-cache.test.ts` — 4 new tests: batch insert, idempotency, empty no-op, no conflict with DSN cache keys ## Test Plan - [x] `bun test test/lib/db/project-cache.test.ts` — 26 tests pass (4 new) - [x] `bun test test/lib/complete.test.ts` — 20 tests pass - [x] `bun run typecheck` — clean - [x] `bun run lint` — clean - Manual: run `sentry project list myorg/`, then `sentry issue list myorg/<TAB>` — projects now complete Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This was referenced Mar 20, 2026
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.

Summary
Add a hybrid static + dynamic shell completion system that suggests cached org slugs, project slugs, and project aliases alongside existing command/subcommand names — with fuzzy matching for typo tolerance.
Approach: Hybrid Static + Dynamic
sentry __completeat runtime, which reads the SQLite cache with fuzzy matchingNew Files
src/lib/fuzzy.ts— Shared Levenshtein distance +fuzzyMatch()with tiered scoring (exact > prefix > contains > Levenshtein distance)src/lib/complete.ts— Completion engine: handles the__completefast-path with context parsing, cache querying, and lazy project fetchingtest/lib/fuzzy.test.ts— Property-based + unit tests for fuzzy matchingtest/lib/complete.test.ts— Tests for the completion engineKey Changes
src/bin.ts:__completefast-path at top ofmain()— skips all middleware, telemetry, and authsrc/lib/completions.ts: ExtendedextractCommandTree()with flag metadata; updated bash/zsh/fish generators with flag completion + dynamic__completecallbacksrc/lib/db/project-cache.ts: AddedgetAllCachedProjects()andgetCachedProjectsForOrg()for completion queriessrc/lib/platforms.ts: Uses sharedlevenshtein()fromfuzzy.tsinstead of local copyHow Tab Completion Works