Worker Threads
VS Code extensions run on a single Node thread shared with language services, file watchers, and UI. If the extension host stalls, so does typing. CodeBuddy pushes every CPU-heavy operation to a worker so the main thread stays under the 16 ms frame budget.
The primary win isn’t throughput — worker spawn overhead is ~50 ms — it’s UI responsiveness. Big analyses no longer freeze the editor.
Inventory
Section titled “Inventory”| Worker | File | Kind | Purpose |
|---|---|---|---|
| Codebase Analysis | src/workers/codebase-analysis.worker.ts | worker_threads | Full-project analysis + call graph |
| AST Analyzer | src/workers/ast-analyzer.worker.ts | worker_threads | File-level chunking for the vector index |
| Embedding | src/workers/embedding-worker.ts | worker_threads (pooled) | Google-GenAI embedding generation |
| Vector DB | src/workers/vector-db-worker.ts | Disabled stub | Legacy LanceDB worker (WORKER_DISABLED = true). The live vector store is SQLite (sqlite-vector-store.ts + hnsw-index.ts) — see WASM & SQLite. |
| Chat History | src/services/chat-history-worker.ts | Async class (not real thread) | SQLite chat persistence |
Codebase Analysis Worker
Section titled “Codebase Analysis Worker”The heaviest. Spawned for CodeBuddy: Analyze Codebase and auto-analysis triggers.
What it produces:
- Dependency analysis —
package.json,requirements.txt,go.mod,Cargo.toml,pom.xml,composer.json,Pipfile,pyproject.toml. - Framework detection — Express, NestJS, Fastify, Flask, Django, Spring, Gin, Actix, Laravel, etc.
- File-content analysis — endpoints, data models, schemas, snippets, import graph (Tree-sitter or regex fallback).
- Architecture — monolith / microservices / CLI / library + patterns (MVC, hexagonal, event-driven).
- Call graph — file import DAG → entry points, hot nodes, cycles.
- Middleware — chains, auth strategies (JWT, OAuth, Session, API Key), error handlers.
Message protocol:
Main → Worker: { type: "ANALYZE_CODEBASE", payload: { workspacePath, files, grammarsPath } } { type: "CANCEL" }
Worker → Main: { type: "ANALYSIS_PROGRESS", progress: { current, total, message } } { type: "ANALYSIS_COMPLETE", payload: AnalysisResult } { type: "ANALYSIS_ERROR", error } { type: "LOG", level, message, data }Memory discipline: snippets capped at 30 files × 75 lines × 3000 chars. Call graph disposed after summary extraction. Import arrays zeroed post-graph. Tree-sitter analyzer disposed in a finally to release WASM. Paths relativized at serialization boundary — no absolute paths leak into LLM context.
Cancellation: service passes a CancellationToken; worker checks isCancelled between phases.
Security: file-path regexes use [^\\/]* instead of .* to avoid catastrophic backtracking. TOML parsing is a line-by-line state machine so # inside URLs/quoted values doesn’t break parsing.
AST Analyzer Worker
Section titled “AST Analyzer Worker”Lightweight. Chunks single files for the vector index.
| Setting | Value |
|---|---|
| Chunk size | 1000 chars |
| Overlap | 200 chars |
| Min chunk | 50 chars (smaller discarded) |
| Chunk ID | {filePath}::{byteOffset} |
Uses web-tree-sitter for AST-aware chunking; falls back to text splitter if WASM fails. No data loss — just less precise chunk boundaries.
Protocol:
Main → Worker: { type: "INDEX_FILE", data: { filePath, content } }Worker → Main: { type: "RESULT", data: { filePath, chunks[] } }Worker → Main: { type: "ERROR", error }Embedding Worker Pool
Section titled “Embedding Worker Pool”Only pooled worker. Up to min(4, os.cpus().length) concurrent.
How it works:
- Init
min(4, os.cpus().length)workers, ping-verify each. - Round-robin batch assignment (
workerId % workers.length). - Each worker iterates its batch, 100 ms between items (rate-limit protection).
- Progress messages aggregate into overall progress (“batch 3/10 at 60%”).
- Per-item error isolation — one failure doesn’t kill the batch.
- 30 s task timeout — silent worker → promise rejects.
Chat History Worker
Section titled “Chat History Worker”Simulates worker semantics (request/response IDs + busy-checking) without spawning a real thread. Keeps SQLite writes off the hot path.
Operations: GET_CHAT_HISTORY, SAVE_CHAT_HISTORY, ADD_CHAT_MESSAGE, GET_RECENT_HISTORY, CLEAR_CHAT_HISTORY, CLEANUP_OLD_HISTORY, SAVE_SUMMARY, GET_SUMMARY, GET_SESSIONS, CREATE_SESSION, GET_CURRENT_SESSION, SWITCH_SESSION, UPDATE_SESSION_TITLE, DELETE_SESSION, GET_SESSION_HISTORY.
Concurrency guard: single-request-at-a-time. Second concurrent request throws immediately — no WAL / mutex needed.
WorkerLogger
Section titled “WorkerLogger”Workers can’t reach vscode.window or OutputChannel. WorkerLogger is a drop-in for the main Logger that serializes messages over parentPort.postMessage({ type: "LOG", ... }). Parent service forwards to the main Logger → OutputChannel + file + telemetry.
Rough numbers
Section titled “Rough numbers”Medium codebase (~1500 files):
| Operation | Without workers | With workers | Improvement |
|---|---|---|---|
| Codebase analysis | ~12 s (UI frozen) | ~8 s (UI responsive) | UI never blocks |
| Embedding 500 chunks | ~45 s (serial) | ~15 s (4 workers) | ~3× throughput |
| AST chunking 200 files | ~4 s (UI frozen) | ~3 s (UI responsive) | UI never blocks |
| Chat history save | ~200 ms (blocks) | ~200 ms (async) | Input never delayed |
Related
Section titled “Related”- WASM — the Tree-sitter + sql.js runtimes workers use
- Code indexing — the AST + embedding pipelines wired together
- Semantic search — consumer of the indexed data