fix(vscode): avoid eager worktree watchers - #12593
Conversation
Code Review SummaryStatus: No Issues Found | Recommendation: Merge The latest commit ( Files Reviewed (3 files)
Previous Review Summary (commit 160b066)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 160b066)Status: 1 Issue Found | Recommendation: Merge (optional cleanup) Overview
Issue Details (click to expand)SUGGESTION
Files Reviewed (3 files)
The gating logic in Reviewed by claude-sonnet-5 · Input: 28 · Output: 3K · Cached: 598.1K Review guidance: REVIEW.md from base branch |
|
@marius-kilocode You're a legend. Thanks |
The indexing-worker disable did not deflake it: the same unhandled EBADF/Invalid handle recurred on the next run with indexing logs absent. The actual native fd source is the PTY connect handler, which wraps even the 404 existence check in locations.get(Location.Ref.make(dir)) and thus builds the full v2 location stack for the throwaway git tmpdir, spawning FFF native index and git-status watcher threads on it (the fanout measured in #12593). Instance disposal awaits locations.invalidate, but FFF's native destroy signals its threads instead of joining them, so native teardown races the fixture's rm -rf and surfaces EBADF on Linux and invalid handle on Windows as an unhandled error between tests. The flake first appeared on main with the v1.17.9 merge, which reworked core pty and this handler. KILO_EXPERIMENTAL_DISABLE_FILEWATCHER (set for unit jobs) does not gate FFF; KILO_DISABLE_FFF does. The test asserts auth gating and a 404, which do not involve file search, so select the ripgrep search layer for this file.
…essions-cpu-usage fix(vscode): avoid eager worktree watchers
Related Issue And Origin
Related resource report: #8941
This PR addresses a concrete watcher fanout discovered while investigating the same class of excessive background CPU and memory use. It does not close #8941 because that report was filed on Windows in April 2026, predates the regression described here, and FFF is disabled by default on Windows. The issue remains relevant as the broader user-facing symptom and should retain its independent investigation.
The regression came from the interaction of two otherwise separate changes:
git switch. fix: keep CLI sidebar branch label in sync with git #12314 fixed that regression by makingKilocodeWatchereagerly build and retain the complete v2LocationServiceMapfor every loaded CLI instance so the.git/HEADwatcher would publishvcs.branch.updated.That tradeoff is appropriate for the standalone TUI, which consumes branch-update events. It was unintentionally applied to the VS Code-managed backend as well. Agent Manager restores many directories into one shared backend, so the eager warmup converted one branch-label fix into one complete FFF index and watcher set per restored worktree. The performance regression therefore appeared after #12314, with the FFF dependency supplied by #12204.
Problem
Agent Manager restores many worktree and session directories into one shared
kilo servebackend. Each restored directory currently initializesKilocodeWatcher, which eagerly builds and retains the complete v2 location stack even when no v2 filesystem feature has been requested for that directory.That eager stack includes
FileSystemSearch. On macOS and Linux,FileSystemSearchselects FFF by default, and FFF creates a repository index plus native filesystem and Git-status watcher threads. The resulting cost scales with every restored Agent Manager worktree rather than with the worktrees that are actively using file search.In the affected production backend, 36 retained worktree locations produced 36
fff-watcher-ownthreads, matching FSEvents and debouncer activity, and 637 open file descriptors. CPU reached 140 to 161 percent and memory reached 3 to 4 GB. Native samples repeatedly ended inGitStatusCache::git_status_for_paths,git_status_list_new, andgit_diff_index_to_workdir, showing that the background indexes were continuously traversing worktrees for Git status.The scaling was consistent across control processes:
Root Cause
KilocodeWatcherwas introduced specifically to restore standalone CLI branch-label synchronization. Its implementation warms the entire location layer because the v2 watcher service lives inside that layer. The warmup is keyed by directory and retained for the instance lifetime.That location layer contains substantially more than
.git/HEADmonitoring. It also constructs filesystem search, plugins, catalogs, and tool registries. Constructing filesystem search selects FFF, and constructing FFF immediately creates its index and background watcher. The branch-label dependency therefore pulled the complete filesystem indexing stack into every loaded instance.The VS Code extension shares one backend across the sidebar, editor tabs, and Agent Manager. Agent Manager can restore dozens of directory-scoped sessions into that process, so the per-instance warmup scaled linearly with historical and active worktrees. FFF was initialized even when the user never opened file mentions or another filesystem feature in those worktrees.
The VS Code extension does not consume
vcs.branch.updated. Agent Manager obtains branch names, worktree status, diffs, and pull request state through extension-host Git operations and polling. The eager branch watcher therefore paid a substantial ongoing cost without serving the VS Code worktree UI.Change
KilocodeWatchernow skips only its eager location-stack warmup when the existing runtime client identity isKILO_CLIENT=vscode. Other clients retain the current behavior.This is intentionally narrower than disabling Core file watching or FFF globally:
@file and folder picker still uses the indexed FFF implementation..git/HEAD, preserving external branch-switch updates in the TUI.The implementation and tests live in Kilo-owned
kilocodepaths. No shared upstream Core filesystem files are changed, which minimizes future OpenCode merge conflicts.Measured Improvement
A controlled VS Code self-test restored the same Agent Manager state before and after suppressing the eager warmup. Before any explicit file search, the resource profile changed as follows:
The remaining initial watcher belonged to a location actually requested by the loaded VS Code surfaces rather than to every restored worktree.
The same run then selected an Agent Manager worktree and queried
@server-manager:This changes resource growth from proportional to every restored Agent Manager worktree to proportional to locations that actually request the v2 location stack. For the original 36-location production case, it removes the source of the 36-way background Git-status fanout rather than merely reducing its polling frequency.
Preserved VS Code Behavior
Agent Manager worktree behavior remains pull-based:
WorktreeManagerandGitOps.GitStatsPollerand the extension diff controllers.file.watcher.updatedandvcs.branch.updatedevents have no current VS Code or Agent Manager consumer.Other Kilo features remain on independent implementations:
globandgreptools continue using Ripgrep directly.read,write,edit, and patch behavior is unchanged. Synthetic edit events are still published.@mentions.Standalone CLI And Release Deployment
The standalone CLI should not disable this watcher globally because its TUI is the feature that consumes branch-update events. The runtime gate defaults to eager behavior for
cli,run, separately launched servers, and unspecified clients. Onlyvscodeskips the unconditional warmup.The change is included in both
@kilocode/cliandkilo-codepatch changesets. A VS Code extension release builds and packages the CLI binary from the same repository revision, then launches that bundled binary with the existingKILO_CLIENT=vscodeenvironment identity. No compile-time feature flag, user setting, or deployment-specific environment configuration is required. Standalone CLI releases include the same code but do not activate the VS Code branch.Scope
This does not attempt to remove every file watcher from Kilo. Watchers that serve an active user feature remain available and are created on demand. In particular, enabled semantic indexing keeps its own watcher, and an FFF-backed file picker keeps the selected location index current. The change removes the unused eager watcher fanout caused solely by restoring Agent Manager worktrees.