Skip to content

fix(indexing): replace chokidar file watcher with @parcel/watcher - #12784

Merged
marius-kilocode merged 10 commits into
Kilo-Org:mainfrom
mavrukin:fix/indexing-file-watcher-ready-timeout
Sep 2, 2026
Merged

fix(indexing): replace chokidar file watcher with @parcel/watcher#12784
marius-kilocode merged 10 commits into
Kilo-Org:mainfrom
mavrukin:fix/indexing-file-watcher-ready-timeout

Conversation

@mavrukin

@mavrukin mavrukin commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Issue

Fixes #12783

Context

Local codebase indexing hangs indefinitely at "Initializing file watcher…" —
zero files indexed, no error surfaced — on large or multi-repo workspaces.

Root cause (validated locally): the indexing FileWatcher used chokidar,
and under the bundled bun runtime chokidar.watch() blocks the event loop
during its initial recursive scan. On even a 2,438-file repo the loop is starved
so badly that nothing runs — no ready, and no setTimeout can fire (so this
branch's original readiness-timeout couldn't help). Node handles the same repo in
~0.2s; bun starves indefinitely. Because the orchestrator awaits the watcher
before vector-store init and the scan, the whole indexing pipeline is gated on a
watcher that never becomes ready.

Implementation

Replace chokidar with @parcel/watcher — the same native, event-based backend
@kilocode/core's workspace watcher already uses (and which runs fine under bun) —
and harden the subscribe lifecycle around it.

  • Non-blocking subscribe. initialize() subscribes for create/update/delete
    with no blocking initial scan and resolves promptly. The loader mirrors
    @kilocode/core's watcher: a static createWrapper import from
    @parcel/watcher/wrapper plus a dynamic require of the platform binding
    (@parcel/watcher-<platform>-<arch>, bun-compile-safe) — a single load path that
    degrades cleanly to "no watcher" when the native binding is unavailable (no
    main-package fallback).
  • Degrade, don't fail (#151). If the backend is missing or subscribe rejects
    (e.g. inotify ENOSPC), initialize() warns and resolves so the full scan still
    produces a searchable index; a transient failure clears this.ready so the next
    run retries.
  • No leaked/late subscription (#245). A subscribe that resolves after
    shutdown()/dispose(), or is superseded by a newer initialize(), is torn down
    via an identity guard instead of stored on a disposed watcher.
  • Prune ignored dirs from the native watch (#134). Kilo's infra dirs plus
    per-repo .gitignore/.kilocodeignore directory patterns are passed to
    parcel's ignore, so large repos don't over-watch (the very thing that triggers
    the ENOSPC degrade). This is a watch-descriptor optimization only — correctness
    stays in shouldIndex() — so it is deliberately conservative: negated (!)
    gitignore patterns are honored, and any ignore-file directory whose name contains
    glob metacharacters (a leading ! negation, or e.g. a Next.js [slug] route) is
    skipped, so a derived prune can never invert into a negation or match the wrong tree.
  • Drop chokidar; add @parcel/watcher@2.5.1; replace WATCHER_READY_TIMEOUT_MS
    with a defensive PARCEL_SUBSCRIBE_TIMEOUT_MS. The subscribe fn is injectable, so
    the whole lifecycle is unit-tested without a real watcher.

Tradeoff worth reviewer attention: parcel's ignore can't express gitignore
negation/ordering, so the derived prune globs intentionally under-prune (skip
anything a re-include could reach) rather than risk silently dropping an indexed file.

Screenshots / Video

N/A — no UI change. The indexing status text is unchanged; the fix is that it now
progresses to completion instead of hanging.

Screenshot 2026-08-04 at 9 04 39 PM

How to Test

Manual/local verification

  • Agent-executed (bun 1.3.14, darwin-arm64): built the fixed FileWatcher and ran
    it against a real ~15-repo umbrella (~275k indexable files). initialize() resolves
    in ~0.2s (was: indefinite hang), the native subscribe succeeds, 879
    gitignore-derived directory prunes are applied, and a real create event flows
    end-to-end through the pipeline.
  • Agent-executed checks: tsgo typecheck clean; bun test … file-watcher.test.ts
    17/17 pass; full bun turbo typecheck (JetBrains incl.) green; oxlint → 0
    errors; prettier → clean.
  • Human-verified: with the fix built into the packaged CLI binary, the contributor
    confirmed in the live VS Code extension that indexing runs to completion — status
    reaches "IDX Complete — File watcher started. Index up-to-date." — where it
    previously hung indefinitely at "Initializing file watcher…".

Reviewer test steps

  1. From packages/kilo-indexing/: bun run typecheck, then
    bun test test/kilocode/indexing/processors/file-watcher.test.ts (17 pass).
  2. The subscribe backend is injectable; the tests cover event mapping (update
    change), ignore/extension filtering, gitignore-prune forwarding (incl. negation
    and [slug]/!scope-metachar-dir skips), degrade-on-failure + retry, and
    late-resolution teardown.

Blocked checks and substitute verification

  • Unit tests use an injected fake backend, so the real @parcel/watcher native
    subscription isn't exercised by them. Substitute verification: confirmed the real
    subscription end-to-end on the ~15-repo umbrella and in the packaged extension
    (above), and confirmed parcel's ignore matching semantics from its wrapper.js
    (globs matched relative to the subscribed root; plain entries resolved to absolute).

Checklist

  • Issue linked above, or exception explained
  • Tests/verification described
  • Screenshots/video included for visual changes, or marked N/A
  • Changeset considered for user-facing changes (.changeset/kilo-indexing-file-watcher.md)
  • I personally reviewed the diff and can explain the changes, including any AI-assisted work.

FileWatcher.initialize() created a chokidar watcher over the whole workspace
and awaited its "ready" event with no timeout. orchestrator.ts awaits
_startWatcher() before vector-store init and the scan, so the entire indexing
pipeline is gated on chokidar reaching "ready". On very large or multi-repo
workspaces, when chokidar's native fsevents backend is unavailable (the bundled
runtime falls back to a slow per-directory walk), or on symlink-heavy trees
(chokidar defaults followSymlinks: true), "ready" may never fire. Because
initialize() hangs rather than rejecting, the orchestrator's error handler never
runs and indexing sits at "Initializing file watcher..." indefinitely with no
error and zero embeddings.

- Bound the readiness wait with WATCHER_READY_TIMEOUT_MS (60s); on timeout,
  reject with an actionable error so the orchestrator surfaces an Error state
  instead of hanging. Reset watcher state so a later initialize() retries
  cleanly.
- Set followSymlinks: false to match the scanner glob and git semantics,
  avoiding symlink cycles/amplification that can stall the initial scan.
- Extract waitForWatcherReady() and add deterministic unit tests.
// this rejected promise.
if (this.ready === ready) this.ready = undefined
if (this.watcher === watcher) this.watcher = undefined
void watcher.close()

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.

WARNING: Fire-and-forget close() can leak the failed watcher and produce an unhandled rejection

Two concerns on this line, both specific to the failure path this PR adds:

  1. void watcher.close() discards the promise, so if close() rejects (quite plausible for a watcher that is already unhealthy — this is exactly the case where the readiness timeout fires) Node reports an unhandled rejection. Attaching a logging .catch() keeps the failure visible without changing control flow.
  2. Because this.watcher is cleared just above, this is the last reference the class holds to the chokidar instance. If close() never settles, the underlying fs.watch handles from the partially-completed initial walk stay open forever, and shutdown()/dispose() can no longer reach them. A retry (which the reset intentionally enables) then creates a second full watcher on top of the first, so repeated retries accumulate watchers and file descriptors.

Something like watcher.close().catch((err) => log.error("failed to close watcher after readiness failure", { err })) addresses (1) and at least makes (2) diagnosable. Also worth noting the add/change/unlink handlers registered above remain attached until close() actually completes, so events from the abandoned watcher can still push entries into accumulatedEvents after initialize() has thrown.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

// own semantics. Following symlinked directories can cause chokidar to
// re-walk/cycle through link targets, which on symlink-heavy trees can
// stall the initial scan so "ready" never fires.
followSymlinks: false,

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.

SUGGESTION: followSymlinks: false also stops incremental updates for symlinked files

The comment justifies this by matching the scanner, and for symlinked directories that is accurate (glob defaults to follow: false, so ** does not descend into them). But glob with nodir: true still returns symlinks that point at files, so those paths are indexed by the initial scan. With followSymlinks: false, chokidar watches the link itself rather than the target, so edits to the target no longer emit change — those entries will silently go stale until a full re-index.

Probably an acceptable trade for the hang this fixes, but it's a behavior change beyond "avoid symlink cycles" and worth calling out in the changeset/PR notes (or narrowing, e.g. keep following links but bound the walk).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

// unavailable (e.g. inside a bundled runtime) and it falls back to a slow
// per-directory walk, "ready" may never fire. Without a bound this hangs the
// entire indexing pipeline indefinitely with no surfaced error.
export const WATCHER_READY_TIMEOUT_MS = 60_000

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.

SUGGESTION: A hard 60s deadline turns a hang into "no indexing at all"

Since orchestrator.runIndexing() awaits _startWatcher() before the vector store init and the scan, a rejection here aborts the whole pipeline and lands in Error state — the user gets zero indexed files instead of an infinite spinner. That is strictly better for diagnosability, but as the PR notes, the watcher is only needed for incremental updates, so a genuinely large monorepo whose initial chokidar walk takes 70s now goes from "eventually works" to "never works".

Given the fix is already touching this path, consider degrading instead of failing: log/telemetry the readiness timeout, skip the watcher (no incremental updates), and let the scan proceed. If the hard failure is intentional for now, a follow-up issue for the degraded mode would be worth linking, and making the bound configurable (as suggested in the PR description) reduces the risk of false failures on slow network filesystems.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

handlers.get(event)?.(arg)
},
}
}

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.

SUGGESTION: The riskiest new logic — initialize()'s failure reset — is untested

The three new tests cover waitForWatcherReady in isolation, which is the simple half. The subtler behavior added by this PR is in initialize(): on failure it must clear this.ready and this.watcher, close the watcher, and allow a later initialize() to retry rather than re-awaiting a rejected promise. None of that is exercised, and a regression there reintroduces a stuck state (a retained rejected this.ready makes every subsequent initialize() throw immediately).

A test against a real temp directory with a very small timeout — or injecting the readiness wait — would cover the retry path without a real hang.

Minor note on the fake: once stores handlers in a Map keyed by event name and never removes them, so it does not actually behave like once, and the name createReadyWatcher is misleading given two of the three tests use it for the non-ready cases.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@kilo-code-bot

kilo-code-bot Bot commented Aug 2, 2026

Copy link
Copy Markdown
Contributor

Code Review Summary

Status: 3 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 2

Incremental review of d9c94b77 (directory expansion, atomic-save create→change, conservative ! prune abort). Previous PR-description fallback finding is resolved. New issues are in expand() / handleFileEvent.

Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-indexing/src/indexing/processors/file-watcher.ts 417 expand() copies every cached path on each incremental batch, including single-file saves that never need child expansion

SUGGESTION

File Line Issue
packages/kilo-indexing/src/indexing/processors/file-watcher.ts 353 directory = true lets non-file events reach overlay.block(); expand() then drops them without settle(), so worktree overlay entries stay blocked
packages/kilo-indexing/src/indexing/processors/file-watcher.ts 440 Directory expansion uses dot: true while the scanner uses dot: false, so hidden paths can be indexed incrementally and dropped on the next full scan
Files Reviewed (5 files)
  • packages/kilo-indexing/src/indexing/processors/file-watcher.ts - 3 issues
  • packages/kilo-indexing/src/indexing/shared/load-ignore.ts
  • packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts
  • packages/kilo-indexing/test/kilocode/indexing/shared/load-ignore.test.ts
  • packages/kilo-indexing/package.json

Fix these issues in Kilo Cloud

Previous Review Summaries (6 snapshots, latest commit 1d267c6)

Current summary above is authoritative. Previous snapshots are kept for context only.

Previous review (commit 1d267c6)

Status: 1 Issue Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 1

Incremental review of 1d267c6f (guard ! in watcher prune globs + simplify the parcel loader). The ! addition to GLOB_META is correct and closes a real hole — a bare-name prune emitted from a !-prefixed directory (e.g. !scope/**/cache) would reach parcel as a negated glob, which negation semantics turn into "ignore everything except that tree". No emitted glob can contain ! now: bodies with ! are skipped, dirs with ! are skipped, and negated lines are diverted before emission. The static createWrapper import and single loading path mirror packages/core/src/filesystem/watcher.ts exactly (@parcel/watcher is bundled, not external, with all platform prebuilds staged by the build), and the new real-fs test fails without the guard. One new suggestion: the PR description still documents the removed main-package fallback.

Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/kilo-indexing/src/indexing/processors/file-watcher.ts 81 PR description still advertises the removed main-package fallback
Files Reviewed (3 files, incremental)
  • packages/kilo-indexing/src/indexing/processors/file-watcher.ts - 1 issue
  • packages/kilo-indexing/src/indexing/shared/load-ignore.ts
  • packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts

Fix these issues in Kilo Cloud

Previous review (commit f15f88b)

Status: No Issues Found | Recommendation: Merge

Incremental review of f15f88b03a (glob-metachar directory guard in pruneGlobs + test). Both previous findings are resolved: ignore files inside glob-metachar directories (e.g. Next.js [slug] routes) no longer emit raw-interpolated prune globs — the dirHasMeta guard skips candidate emission while still collecting negations for shadow-safety — and parcel's ignore matching semantics (globs relative to the subscribed root, plain entries resolved to absolute paths) are now documented on pruneGlobs. The new test exercises the metachar-directory case against the real loadIgnore and would fail without the guard.

Files Reviewed (2 files, incremental)
  • packages/kilo-indexing/src/indexing/shared/load-ignore.ts
  • packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts

Previous review (commit 3b4cc06)

Status: 2 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 0
SUGGESTION 2

Incremental review of 3b4cc0688d (gitignore-derived watch pruning + superseded-subscription teardown + retry-on-failure + changeset). All five previous findings are resolved: the late-resolving subscription is now torn down instead of leaked (this.ready !== ready guard + test), a failed subscribe resets this.ready so the next run retries, the late-rejection log no longer mislabels the failure, per-repo gitignored dirs are pruned via the new watchIgnoreGlobs(), and a changeset was added. The two remaining items are both in the new pruneGlobs logic and both are non-blocking.

Issue Details (click to expand)

SUGGESTION

File Line Issue
packages/kilo-indexing/src/indexing/shared/load-ignore.ts 135 entry.dir is interpolated into watcher globs without escape() (unlike rules()); a .gitignore inside a metachar directory (e.g. Next.js [slug] routes) produces a glob that prunes the wrong tree and can silently stop incremental updates for an unrelated path.
packages/kilo-indexing/src/indexing/shared/load-ignore.ts 146 The new root-relative globs (data, pkg/**/build) only prune if @parcel/watcher matches ignore against root-relative paths; if it matches absolute paths they silently no-op. Unit tests can't observe native pruning — worth confirming against a real subscription.
Files Reviewed (4 files, incremental)
  • packages/kilo-indexing/src/indexing/shared/load-ignore.ts - 2 issues
  • packages/kilo-indexing/src/indexing/processors/file-watcher.ts - no issues (previous findings resolved; supersede/teardown and retry logic verified against shutdown/dispose paths)
  • packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts - no issues (new tests cover late-resolution teardown, transient-failure retry, and negation shadowing)
  • .changeset/kilo-indexing-file-watcher.md - no issues (resolves the carried-forward changeset finding)

Fix these issues in Kilo Cloud

Previous review (commit 0a12bda)

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 4

Incremental review of 0a12bdabc8 (degrade gracefully when the native watcher is unavailable). The previous WARNING (a rejected subscribe propagated through initialize() and broke all indexing) is resolved — initialize() now logs a warning and lets the full scan proceed. The silent .catch(() => {}) and the untested failure path are also resolved.

Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-indexing/src/indexing/processors/file-watcher.ts 245 A subscribe that resolves after shutdown()/dispose() (cancel during the up-to-10s subscribe window) stores a live native subscription on a disposed watcher; nothing unsubscribes it and its events grow accumulatedEvents unboundedly. A later initialize() can even overwrite it with a second subscription, leaking the first permanently.

SUGGESTION

File Line Issue
packages/kilo-indexing/src/indexing/processors/file-watcher.ts 248 The .catch latches a failed subscribe into a resolved this.ready, so transient errors (e.g. temporary inotify ENOSPC) disable incremental updates until settings change / reload. Consider resetting this.ready so the next startIndexing retries while still not throwing.
packages/kilo-indexing/src/indexing/processors/file-watcher.ts 111 A late rejection of pending (subscribe itself failing after the timeout) is logged as "failed to tear down late file watcher subscription" — nothing was torn down; the message mislabels the root cause.
packages/kilo-indexing/src/indexing/processors/file-watcher.ts 134 (Carried forward, unchanged) The parcel ignore list dropped .gitignore/.kilocodeignore; chokidar's ignored callback pruned those directories from traversal, so inotify now watches them, multiplying watch descriptors.
(repo-wide) - (Carried forward) Still no changeset for a user-facing fix; @kilocode/kilo-indexing is a versioned package — add a .changeset/*.md with a user-facing description.
Files Reviewed (3 files, incremental)
  • packages/kilo-indexing/src/indexing/processors/file-watcher.ts - 3 issues
  • packages/kilo-indexing/src/indexing/constants/index.ts - no issues (comment-only change)
  • packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts - no issues (degrade test correctly pins the new no-throw / no-retry behavior)

Context read (unchanged): orchestrator.ts (_startWatcher/stopWatcher/cancelIndexing paths), manager.ts (orchestrator reuse across runs), runtime.ts (Emitter.dispose makes post-dispose fire a no-op).

Fix these issues in Kilo Cloud

Previous review (commit 07b082e)

Status: 4 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 3
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-indexing/src/indexing/processors/file-watcher.ts 151 A rejected parcelSubscribe (missing native prebuild, or inotify ENOSPC) propagates through initialize() to orchestrator.ts:254, so state becomes Error and nothing is indexed — despite the message claiming only "incremental index updates are disabled". Consider degrading to scan-without-watcher.

SUGGESTION

File Line Issue
packages/kilo-indexing/src/indexing/processors/file-watcher.ts 164 The parcel ignore list dropped .gitignore/.kilocodeignore. chokidar's ignored callback pruned those directories from traversal; inotify will now watch them, multiplying watch descriptors and risking ENOSPC — so it isn't purely an efficiency prune.
packages/kilo-indexing/src/indexing/processors/file-watcher.ts 130 .catch(() => {}) silently swallows a failed late-teardown unsubscribe(), hiding a real watcher leak; every other teardown path in the file logs.
(repo-wide) - Still no changeset for a user-facing fix (indexing hanging at "Initializing file watcher…"). @kilocode/kilo-indexing is a versioned package; add a .changeset/*.md with a user-facing description.
Files Reviewed (5 files)
  • packages/kilo-indexing/src/indexing/processors/file-watcher.ts - 3 issues
  • packages/kilo-indexing/src/indexing/constants/index.ts - no issues (the 60s deadline is gone; 10s matches packages/core/src/filesystem/watcher.ts)
  • packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts - no issues (injected fake backend now covers subscribe/map/reset-retry/unsubscribe with no mocking library)
  • packages/kilo-indexing/package.json - no issues (@parcel/watcher@2.5.1 matches core and opencode)
  • bun.lock - no issues

Context read (unchanged): orchestrator.ts, service-factory.ts, file/ignore.ts, packages/core/src/filesystem/watcher.ts, packages/opencode/script/build.ts. Verified the createWrapper + platform-binding load and KILO_LIBC define mirror core and that the build installs all-platform @parcel/watcher; verified ignoreInstance is always passed to FileWatcher; verified no stale references to waitForWatcherReady / WATCHER_READY_TIMEOUT_MS / chokidar remain in kilo-indexing. All four findings from the previous review are resolved by the rewrite.

Fix these issues in Kilo Cloud

Previous review (commit 61ec24d)

Status: 5 Issues Found | Recommendation: Address before merge

Overview

Severity Count
CRITICAL 0
WARNING 1
SUGGESTION 4
Issue Details (click to expand)

WARNING

File Line Issue
packages/kilo-indexing/src/indexing/processors/file-watcher.ts 220 void watcher.close() drops the promise (unhandled rejection) and is the last reference to the watcher after this.watcher is cleared, so a non-settling close() leaks fs handles and retries stack watchers

SUGGESTION

File Line Issue
packages/kilo-indexing/src/indexing/processors/file-watcher.ts 201 followSymlinks: false stops change events for symlinked files that the scanner still indexes, so they can silently go stale
packages/kilo-indexing/src/indexing/constants/index.ts 43 Hard 60s deadline aborts the whole pipeline; consider degrading to scan-without-watcher instead of failing, and/or making the bound configurable
packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts 405 initialize()'s failure/reset/retry path is untested; fake once never removes handlers and createReadyWatcher is misnamed
(repo-wide) - No changeset for a user-facing behavior change (hang → visible Error state). @kilocode/kilo-indexing is a versioned package; consider adding .changeset/*.md with a user-facing description
Files Reviewed (3 files)
  • packages/kilo-indexing/src/indexing/processors/file-watcher.ts - 2 issues
  • packages/kilo-indexing/src/indexing/constants/index.ts - 1 issue
  • packages/kilo-indexing/test/kilocode/indexing/processors/file-watcher.test.ts - 1 issue

Context read (unchanged): orchestrator.ts, scanner.ts. Verified that a rejection from initialize() reaches runIndexing()'s catch at orchestrator.ts:255 and surfaces as Error, and that the scanner's glob call does not follow symlinked directories.

Fix these issues in Kilo Cloud


Reviewed by grok-4.6 · Input: 152.3K · Output: 36.3K · Cached: 1.4M

Review guidance: REVIEW.md from base branch main

The previous chokidar-based FileWatcher blocked the event loop during its
initial recursive scan under the bundled bun runtime, so the watcher never
emitted "ready" and indexing hung indefinitely at "Initializing file
watcher..." with zero files indexed. A setTimeout-based readiness bound (the
first attempt on this branch) could not fire because the loop was starved.

Verified locally: chokidar v4 under bun starves the event loop on even a
~2.4k-file repo (node handles the same repo in 0.2s), while @parcel/watcher
subscribes without a blocking JS-side walk and stays responsive even on a
~15-repo umbrella. A real create/update/delete then flows end-to-end through
the incremental pipeline.

Switch the indexing FileWatcher to @parcel/watcher — the same native backend
the @kilocode/core workspace watcher already uses:

- initialize() subscribes (create/update/delete) with no blocking initial scan
  and no readiness timeout gating indexing startup; it resolves promptly.
- The native binding is loaded via createWrapper(require(platform-binding)) for
  the bundled bun-compiled runtime, with a main-package fallback for dev/tests.
- The subscribe function is injectable so the subscribe / event-mapping / retry
  / teardown paths are unit-tested without a real filesystem watcher.
- shutdown()/dispose() unsubscribe with a logging .catch() so a failed teardown
  cannot surface an unhandled rejection.
- Drop the chokidar dependency; add @parcel/watcher; replace
  WATCHER_READY_TIMEOUT_MS with a defensive PARCEL_SUBSCRIBE_TIMEOUT_MS.
@mavrukin mavrukin changed the title fix(indexing): prevent indefinite hang in file watcher initialization fix(indexing): replace chokidar file watcher with @parcel/watcher Aug 3, 2026
@mavrukin

mavrukin commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Thanks for the review. Validating the original (chokidar + readiness-timeout) approach locally surfaced a deeper problem that changes the fix, so I've pushed a new commit that replaces chokidar with @parcel/watcher. Details, and how it relates to each point:

Why the approach changed: under the bundled bun runtime, chokidar.watch() blocks the event loop during its initial recursive scan. On a 2,438-file repo the loop is starved so completely that no ready fires and no setTimeout can fire — so the readiness timeout I first added would never trigger under bun. Node handles the same repo in 0.2s. @parcel/watcher (the backend @kilocode/core's workspace watcher already uses) subscribes without a blocking JS-side walk and stays responsive even on a ~15-repo umbrella; a real create/update/delete flows end-to-end. So the watcher backend itself was the issue.

Against the specific comments:

  1. Fire-and-forget close() / unhandled rejection / leaked watcher — resolved. There's a single parcel subscription now, torn down via subscription.unsubscribe().catch((err) => log.error(...)) in both shutdown() and dispose(), and no per-event handlers that outlive teardown.
  2. followSymlinks:false dropping symlinked-file updates — no longer applicable; chokidar is removed.
  3. Hard 60s deadline turning a hang into "never indexes" — removed. There's no blocking initial scan to bound, and initialize() no longer imposes a readiness deadline on startup. (The remaining PARCEL_SUBSCRIBE_TIMEOUT_MS is only a defensive guard around the native subscribe() call, which returns in well under a second.)
  4. Untested initialize() failure/reset path — now covered. The subscribe function is injectable, so there are tests for: event mapping (update → change), ignore/extension filtering, the failure→reset→retry path (subscribe fails once, initialize() rejects and resets, a later initialize() re-subscribes), and shutdown-unsubscription. The misleading once/createReadyWatcher fake is gone.

Local checks: bun test … file-watcher.test.ts 12/12, bun turbo typecheck green, oxlint 0 errors, prettier clean.

if (!parcel) {
return Promise.reject(
new Error(
"Native file watcher backend (@parcel/watcher) is unavailable; incremental index updates are disabled.",

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.

WARNING: A missing/failing native backend now breaks all indexing, not just incremental updates

The message says "incremental index updates are disabled", but this is a rejection, and initialize() propagates it. orchestrator.ts:188 awaits _startWatcher() before vectorStore.initialize() and the scan, so the catch at orchestrator.ts:254 sets Error (Failed during initial scan: ...) and calls stopWatcher() — zero files get indexed.

That's a behavior regression versus chokidar, which was pure JS and always loaded:

  • No @parcel/watcher-<platform>-<arch> prebuild (e.g. freebsd, or an arch/libc combo that isn't published) → loadParcelWatcher() returns undefined → indexing is completely unavailable where it previously worked.
  • On Linux, parcel.subscribe also rejects when inotify_add_watch hits max_user_watches (ENOSPC) — a very common failure on large monorepos, and exactly the "large repo" case this PR is fixing.

Since the watcher is only needed for incremental updates, consider degrading instead of failing: log a warning, leave this.subscription unset, and let initialize() resolve so the vector-store init + full scan still run. That preserves the user-visible improvement (a searchable index) even when the native watcher can't be established, and matches what the error message already promises.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

}
onEvents(events)
},
{ ignore: watcherIgnoreGlobs(), backend: watcherBackend() },

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.

SUGGESTION: The ignore list dropped .gitignore / .kilocodeignore, which were a prune, not just a filter

The old chokidar ignored callback consulted this.ignoreInstance too, and chokidar calls ignored for directories, so gitignored trees were never descended into. ignoreInstance is always supplied here (service-factory.ts:260), so this is a real loss.

With only FileIgnore.PATTERNS, @parcel/watcher's inotify backend will now recursively watch every gitignored directory that isn't in that hardcoded list — .venv, venv, .tox, Pods, Carthage, packages/*/build variants, generated code, large data dirs, etc. On Linux each directory costs an inotify watch descriptor, so this can push a big repo past max_user_watches and make subscribe reject with ENOSPC — which (see the comment on line 151) currently fails the whole pipeline.

So it isn't purely an efficiency prune as the doc comment on line 105 claims. Consider deriving extra globs from this.ignoreInstance (or threading an ignore list into FileWatchSubscribe so parcelSubscribe can receive them) to restore the previous pruning behavior.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

} catch (error) {
// If the timeout won the race, ensure a late-arriving subscription is still
// torn down instead of leaking.
void pending.then((subscription) => subscription.unsubscribe()).catch(() => {})

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.

SUGGESTION: Don't swallow the late-teardown failure silently

.catch(() => {}) is an empty rejection handler, which AGENTS.md calls out ("No empty catch blocks") for the same reason it matters here: if a late-arriving subscription fails to unsubscribe(), the watcher genuinely leaks and there is no trace of it anywhere. Every other teardown path in this file already logs (shutdown(), dispose()), so this is the odd one out.

Suggested change
void pending.then((subscription) => subscription.unsubscribe()).catch(() => {})
void pending
.then((subscription) => subscription.unsubscribe())
.catch((err) => log.error("failed to unsubscribe late file watcher subscription", { err, workspacePath: directory }))

Note this handler also runs when pending itself rejected (not just on timeout), so the log message should read as "best-effort cleanup" rather than implying a leak every time.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

Address PR review feedback and trim comments:

- initialize() now warns and continues (the full scan still runs) instead of
  rejecting when @parcel/watcher can't be loaded or subscribe fails (e.g. an
  inotify ENOSPC on a large Linux repo). The watcher only powers *incremental*
  updates, so its absence must not fail the whole index.
- Replace the empty late-teardown `.catch(() => {})` with a best-effort handler
  that logs only a genuine late-unsubscribe failure (AGENTS.md: no empty catch).
- Correct the ignore-globs comment: per-repo .gitignore/.kilocodeignore are
  enforced in shouldIndex(), not pruned from the native watch.
- Update the failure test to assert graceful degradation instead of throw/retry.
- Trim the comments added by this PR to the minimum.
@mavrukin

mavrukin commented Aug 3, 2026

Copy link
Copy Markdown
Contributor Author

Thanks — addressed the three points in 0a12bda:

  1. :151 — a missing/failing backend broke all indexing. Fixed by degrading. initialize() now catches a subscribe failure, logs a warning (file watcher unavailable; continuing without incremental updates), leaves this.subscription unset, and resolves — so vectorStore.initialize() and the full scan still run and produce a searchable index. This covers both a missing @parcel/watcher-<platform>-<arch> prebuild and subscribe rejecting (e.g. inotify ENOSPC). Added a test asserting initialize() resolves (doesn't throw) and leaves no subscription on failure.

  2. :130 — empty .catch(() => {}). Replaced with a best-effort handler that tears down a late-arriving subscription and logs only a genuine late-unsubscribe() failure (gated on the timeout having fired), so a normal subscribe-rejection — which is already surfaced via the race — isn't mislabeled as a leak.

  3. :164 — lost gitignore prune. You're right that correctness-vs-watch-scope was conflated; I corrected the comment (per-repo .gitignore/.kilocodeignore are enforced in shouldIndex(), not pruned from the native watch). I did not re-derive gitignore globs for the parcel ignore list in this PR: load-ignore only exposes ignores() (not the raw patterns), converting gitignore→glob safely (anchoring, negations) is non-trivial, and @kilocode/core's workspace watcher has the same behavior today. With the degrade in (1), an ENOSPC from over-watching is now non-fatal (indexing still completes via the full scan). Happy to follow up by threading an ignore list through FileWatchSubscribe if you'd prefer the prune restored.

Verified locally (bun 1.3.14): initialize() on the real backend resolves in ~40ms and a real create/update/delete flows through end-to-end; degrade path unit-tested; bun turbo typecheck green (incl. JetBrains), oxlint 0 errors, prettier clean.

})
this.ready = this.subscribeFn(this.workspacePath, this.onWatchEvents)
.then((subscription) => {
this.subscription = subscription

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.

WARNING: A subscription that resolves after shutdown()/dispose() is leaked and keeps feeding accumulatedEvents

Neither shutdown() nor dispose() awaits this.ready, and this.subscription is still undefined while a subscribe is in flight. If indexing is cancelled (or stopWatcher() runs) while subscribeFn is still pending — possible for up to PARCEL_SUBSCRIBE_TIMEOUT_MS on a pathological backend — the late resolution lands here and stores a live native subscription on an already-disposed watcher. Nothing ever unsubscribes it, and its events keep hitting handleFileEvent, which appends to accumulatedEvents (line 322) even when not collecting — unbounded growth in a long-lived extension host. Worse, dispose() resets this.ready, so a later initialize() starts a second subscribe; when both resolve, this assignment overwrites the first and leaks it permanently.

Consider capturing the pending promise (const ready = ...) and only storing the subscription when it is still current (if (this.ready === ready) ... else void subscription.unsubscribe()), or awaiting this.ready in shutdown() before tearing down.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

this.subscription = subscription
log.info("file watcher subscribed", { workspacePath: this.workspacePath })
})
.catch((error) => {

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.

SUGGESTION: Latching the failure into a resolved this.ready removes retry for transient subscribe errors

Because this .catch converts a failed subscribe into a fulfilled ready, every later initialize() (each startIndexing_startWatcher) no-ops on the early return. A transient failure — e.g. inotify ENOSPC that is freed a minute later — leaves incremental updates disabled until something disposes the watcher (settings change / extension reload). The previous revision reset this.ready = undefined on failure so the next run retried. Consider resetting this.ready in this catch as well (still resolving rather than throwing) so the next indexing run re-attempts the subscription, or call out the trade-off explicitly.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

void pending
.then((subscription) => (timedOut ? subscription.unsubscribe() : undefined))
.catch((err) => {
if (timedOut) log.warn("failed to tear down late file watcher subscription", { err, workspacePath: directory })

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.

SUGGESTION: A late rejection of pending is logged as a teardown failure, mislabeling the root cause

This .catch handles two different errors: (a) unsubscribe() failing while tearing down a late-arriving subscription, and (b) pending itself rejecting after the timeout already fired. In case (b) the subscribe failed — there was never anything to tear down — yet it is logged as "failed to tear down late file watcher subscription", while initialize() already logged the timeout as the failure reason. The real error is preserved in err, but the message points at the wrong operation; consider distinguishing the cases (e.g. a rejection handler on .then that logs "file watcher subscribe failed after the timeout").


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

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.

@kilocode-bot fix it

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.

To use Kilo from GitHub you first need to link your GitHub account to Kilo. Link your Kilo account to continue. After linking, mention me again in this issue or pull request.

…ibe lifecycle

Addresses the review on the @parcel/watcher switch:

- Prune per-repo gitignored directories from the native watch, not just Kilo's
  infra dirs. initialize() unions the FileIgnore globs with best-effort
  directory prunes derived from the ignore matcher and threads them into the
  subscribe call. Without this, inotify watches every gitignored tree (.venv,
  Pods, generated/data dirs, ...) and a large repo can exhaust max_user_watches
  (ENOSPC) -- the very failure this PR otherwise only degrades on. The prune is
  negation-safe: only non-negated, non-glob directory patterns are emitted and
  any candidate a re-include (!) could reach is dropped, so the prune set stays
  a subset of never-indexed paths; correctness stays in shouldIndex().
- Fix a subscription leak: a subscribe that resolves after shutdown()/dispose()
  or is superseded by a newer initialize() is now unsubscribed instead of
  stored on a disposed watcher, where it would leak and keep growing
  accumulatedEvents.
- Reset this.ready on a subscribe failure so a transient error (e.g. inotify
  ENOSPC) is retried on the next indexing run rather than latching incremental
  updates off until reload.
- Distinguish a late subscribe rejection from an unsubscribe teardown failure so
  the timeout path no longer mislabels the root cause.
- Add a changeset for @kilocode/kilo-indexing.
continue
}

const scoped = entry.dir ? `${entry.dir}/${body}` : body

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.

SUGGESTION: entry.dir is interpolated into watcher globs without escaping

rules() (the correctness path just above) runs directory names through escape() before building patterns, but scoped here — and the globs derived from it on lines 146/149 — uses entry.dir raw. A .gitignore living in a directory whose name contains glob metacharacters (e.g. a Next.js app/blog/[slug]/ route dir with its own ignore file) yields a prune glob like app/blog/[slug]/**/build, where the matcher reads [slug] as a character class. That fails to prune the intended tree and can instead prune an unrelated path such as app/blog/s/... — silently stopping incremental updates for files under it.

Since escape() already exists in this file, consider applying it here (escape(entry.dir)), or skip candidates whose dir matches GLOB_META (under-pruning is the safe direction, as the comment above notes).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.


if (anchored || body.includes("/")) {
// Anchored/pathful: a specific subtree, matched relative to root.
candidates.push({ segment: scoped, glob: scoped, scoped: true })

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.

SUGGESTION: Confirm @parcel/watcher matches ignore globs relative to the subscribed root — these root-relative forms silently no-op if it matches absolute paths

Every glob this PR previously passed to parcel was **/-prefixed (watcherIgnoreGlobs()), which matches regardless of the base the native backend matches against. The two new emission forms here and on line 149 (data, pkg/**/build) are root-relative, so they only prune if parcel matches ignore entries against paths relative to the subscribed directory. If matching is done against full paths (I couldn't verify from this checkout — no installed package — but the native backends traverse full paths), these never fire, and the per-repo gitignore pruning advertised in the changeset would only work for bare root-level names like **/.venv.

The unit tests assert the globs reach subscribe but can't observe what the native backend actually prunes, and this scoped/anchored branch has no emission test either. Worth a quick check against a real subscription; if matching turns out to be absolute, prefixing scoped globs with the workspace root would make them robust under either semantics (core already passes absolute paths to parcel via protecteds()).


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

The gitignore-derived watcher prune globs interpolated the ignore file's
directory (entry.dir) raw, unlike rules() which runs it through escape(). A
.gitignore inside a directory whose name contains glob metacharacters (e.g. a
Next.js `app/.../[slug]/` route) produced a glob like `app/[slug]/**/x` where
`[slug]` is a character class, pruning the wrong tree and silently dropping
incremental updates for an unrelated path.

Skip emitting a prune glob when entry.dir contains glob metacharacters,
mirroring the existing skip for metachar pattern bodies; those directories stay
watched and are filtered by shouldIndex(). Negations are still collected so
shadow-safety is preserved. Also documents that parcel matches glob ignore
entries relative to the subscribed root and resolves plain entries to absolute
paths, so the anchored plain forms prune correctly.

Adds a test for a .gitignore inside a `[slug]` directory.
@mavrukin

mavrukin commented Aug 4, 2026

Copy link
Copy Markdown
Contributor Author

Addressed both suggestions in f15f88b:

  1. load-ignore.ts:135 — unescaped entry.dir (real over-prune). Good catch. rules() escapes the dir but pruneGlobs() interpolated it raw, so a .gitignore inside a glob-metachar directory (e.g. a Next.js app/.../[slug]/ route) emitted a glob like app/[slug]/**/x where [slug] is a character class — pruning the wrong tree and silently dropping incremental updates there. Rather than escape (which is fragile for the anchored plain-path forms, since is-glob/path.resolve don't round-trip backslash escapes), I skip emitting a prune glob when entry.dir contains glob metacharacters — mirroring the existing skip for metachar pattern bodies. Those directories simply stay watched and are filtered by shouldIndex(); the heavy dirs (node_modules, etc.) are still pruned via the **/-prefixed FileIgnore globs regardless, so the optimization loss is negligible and there's zero over-prune risk. Negations are still collected so shadow-safety is preserved. Added a test with a .gitignore inside a [slug] directory.

  2. load-ignore.ts:146 — confirm parcel matches ignore relative to root. Verified this is correct, not a no-op: per @parcel/watcher's wrapper.js, entries are split by is-glob — glob entries (those containing *, i.e. **/name and dir/**/name) are matched via micromatch against paths relative to the subscribed root, and plain entries (the anchored data / pkg/build forms) are path.resolve(root, entry)'d to absolute paths — so both forms prune correctly. I also confirmed end-to-end against a real subscription on a 15-repo workspace (879 derived prunes forwarded, native subscription active, real create/update/delete events flowing). Added a comment documenting this so it isn't left to assumption.

Verified locally: tsgo typecheck clean, 17/17 watcher tests (incl. the new [slug] case), oxlint 0 errors, prettier clean, and the full pre-push turbo typecheck (JetBrains included) green.

@marius-kilocode

Copy link
Copy Markdown
Collaborator

@mavrukin can you check if those are correct?

P1 / merge blocker: native ignore globs can crash the indexing worker on Windows.
file-watcher.ts:92-94 and file-watcher.ts:146 always pass globs such as **/*.log to Parcel. @parcel/watcher issue #250 demonstrates an uncatchable native stack overflow on Windows with paths around 300 characters when any glob ignore is active. The PR uses affected version 2.5.1. Linux may also be affected. Because indexing is isolated in a worker, this should not crash the whole Kilo process, so I would not classify it as an application-wide P0, but it is a platform merge blocker.

P1: repository names can alter Parcel glob semantics and silently disable incremental indexing.
load-ignore.ts:106, load-ignore.ts:128, and load-ignore.ts:158 interpolate the ignore file’s directory into a glob without accounting for !. I reproduced a repository directory named !scope generating:
!scope/**/generated
Parcel interprets the leading ! as glob negation rather than a literal directory. This can suppress unrelated watcher events, leaving the index stale after the initial scan. Similar semantic mismatches exist around significant whitespace and complex negated gitignore rules.

P2: teardown retains a native process-exit risk.
file-watcher.ts:332-340 starts unsubscribe() without awaiting it. Parcel 2.5.1 has an open native destruction-order crash report, @parcel/watcher issue #258. Normal shutdown() awaits teardown, but cancellation uses synchronous dispose() through orchestrator.ts:398-400.

@marius-kilocode

Copy link
Copy Markdown
Collaborator

Could we simplify the Parcel loader to match the existing implementation in packages/core/src/filesystem/watcher.ts?

  • Move createWrapper to a static top-level import from @parcel/watcher/wrapper (with the existing @ts-ignore for the missing declarations).
  • Keep the platform-specific binding as a dynamic require(...); Bun needs that for the compiled multi-platform binaries.
  • Remove the fallback require("@parcel/watcher") path and degrade cleanly when the direct native binding cannot load, as the core watcher already does.
  • Keep the typeof import("@parcel/watcher") annotations; those are type-only and do not produce runtime imports.

That gives us one predictable loading path and keeps the indexing watcher aligned with the already-shipped core watcher.

…el loader

Addresses review feedback from @marius-kilocode:

- Add `!` to the glob-metacharacter guard in pruneGlobs(). A repository whose
  directory name contains `!` (e.g. `!scope`) previously emitted a prune glob
  like `!scope/**/generated`, which parcel reads as a negation and could
  suppress unrelated watcher events, silently leaving the index stale. `!` is
  now skipped alongside the other metacharacters (matching discovery()'s set),
  so such directories stay watched and are filtered by shouldIndex().

- Simplify the parcel loader to match @kilocode/core's watcher: hoist
  createWrapper to a static top-level import from `@parcel/watcher/wrapper`,
  keep the platform binding as a dynamic require (bun needs it for compiled
  multi-platform binaries), and drop the `@parcel/watcher` main-package fallback
  so there is one loading path that degrades cleanly when the native binding is
  unavailable.
parcelModule = createWrapper(binding) as typeof import("@parcel/watcher")
} catch {
// Single loading path: degrade cleanly when the native binding is unavailable
// (no @parcel/watcher main-package fallback), exactly like the core watcher.

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.

SUGGESTION: PR description still advertises the removed main-package fallback

The Implementation section of the PR description says the native binding loads "with a main-package fallback for dev/CLI/tests", but this commit deletes that fallback — loadParcelWatcher() now has a single loading path and degrades when the platform binding is missing. The simplification itself is fine (it does mirror packages/core/src/filesystem/watcher.ts exactly, and the degrade path is covered by tests), but the stale description will send reviewers looking for a fallback that no longer exists. Worth updating the PR body.


Reply with @kilocode-bot fix it to have Kilo Code address this issue.

@mavrukin

Copy link
Copy Markdown
Contributor Author

Thanks @marius-kilocode — verified all four against the code and both upstream issues. Two are fixed in 1d267c6; the two native-crash ones are upstream Parcel items, not something introduced by this change.

✅ Fixed

! in a directory name → negation glob. Confirmed. My earlier metachar guard for [slug]-style dirs used /[*?[\]{}()]/ and omitted !, even though the module's own discovery() already treats ! as special ("*!?[]{}()"). A dir like !scope emitted !scope/**/generated, which Parcel reads as negation. ! is now in the guard, so such dirs stay watched and are filtered by shouldIndex(); added a !scope test case.

Loader simplification. Done, mirroring @kilocode/core: createWrapper is a static top-level import from @parcel/watcher/wrapper (with the @ts-ignore), the platform binding stays a dynamic require(...) for the bun-compiled multi-platform binaries, and the require("@parcel/watcher") fallback is removed so there's a single load path that degrades to "no watcher" cleanly.

🔺 Verified — upstream Parcel items, not this change

#250 (glob ignore → native stack overflow on long paths). Yep, confirmed. But it's already latent in the shipped @kilocode/core watcher, which passes the same glob ignores (**/*.log, **/*.swp, …) to Parcel 2.5.1 in-process — so this PR doesn't introduce it. It's an upstream Parcel item (Glob.cc uses a recursive std::regex), not something to change here.

#258 (crash at process exit, static destruction order). Confirmed too, and the same story: an upstream Parcel exit-time defect that affects any embedder including the core watcher, not introduced by this change.

Net: 1d267c6 handles the two actionable items; #250/#258 are upstream Parcel items rather than anything in this change.

@marius-kilocode marius-kilocode left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Thanks for sticking with this and working through the feedback, @mavrukin. Verified the compiled build in isolated VS Code on macOS: 2,502-file startup, search, atomic saves, folder moves/replacements/deletes, and ignore re-includes all passed with local test providers.

@marius-kilocode
marius-kilocode merged commit bbc26e4 into Kilo-Org:main Sep 2, 2026
30 checks passed
@marius-kilocode

Copy link
Copy Markdown
Collaborator

I also applied another fix to get this through. Some of the pruning will be broken on Windows due to the / logic. But that should not be a blocker for now.

@mavrukin

mavrukin commented Sep 2, 2026

Copy link
Copy Markdown
Contributor Author

I also applied another fix to get this through. Some of the pruning will be broken on Windows due to the / logic. But that should not be a blocker for now.

Thanks appreciate it

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Codebase indexing hangs indefinitely at "Initializing file watcher…" on large / multi-repo workspaces (no timeout, no error)

2 participants