fix(jetbrains): make Agent Manager worktree deletion non-blocking and fault tolerant - #13887
Merged
Merged
Conversation
… fault tolerant Deleting a worktree with a large node_modules blocked the remove RPC for 25-30s (git worktree remove --force runs the recursive delete synchronously), and status polling running concurrently spawned git/gh processes against the directory mid-delete, throwing "Unable to read current working directory" and cancelling every other worktree's stats/dirty/PR result through the shared parallel() scope. remove() now atomically renames the worktree to a `.kilo-delete-<uuid>` sibling (matching the VS Code extension's WorktreeManager convention), prunes git's metadata, deletes the branch, and returns immediately while the actual recursive delete runs in the background via a new WorktreeTrash service. A worktree still considered locked without `force` skips staging entirely and goes through the existing synchronous git remove, since a filesystem rename cannot see or honor a git lock. Every read path (stats, dirty, prStatus, branchStatus, the gh probe) now skips a worktree WorktreeTrash reports as doomed, and per-item stats/dirty failures are isolated so one bad worktree can no longer fail the whole batch. Git-mutating RPCs (create/import/remove/rename/adopt/reorder/session list) are now serialized per repository with a Mutex to stop concurrent calls from interleaving `worktree list`/`remove`/`prune` or racing the `.kilo/jetbrains.json` read-modify-write. Failures now log with elapsed time and a timeout flag instead of a blank error.
Contributor
Code Review SummaryStatus: No Issues Found | Recommendation: Merge Files Reviewed (10 files)
Previous Review Summary (commit 05c1bb0)Current summary above is authoritative. Previous snapshots are kept for context only. Previous review (commit 05c1bb0)Status: 5 Issues Found | Recommendation: Address before merge Overview
Issue Details (click to expand)WARNING
SUGGESTION
Files Reviewed (10 files)
Reviewed by grok-4.6 · Input: 157.9K · Output: 17.8K · Cached: 576.3K Review guidance: REVIEW.md from base branch |
- WorktreeTrash keyed marks on a normalization-only path and recorded every identity the path resolved to at mark() time. Keying on toRealPath() meant unmark() recomputed a different key once the checkout had been renamed away, leaking the entry for the rest of the session and keeping a worktree later recreated at the same slug invisible to every poll. - stage() now demands ATOMIC_MOVE. A plain Files.move degrades to copy-then-delete across filesystems, which would block the caller for a full tree copy and leave the original beside a half-built sibling instead of failing over to the synchronous git fallback the KDoc promises. - Reaps and sweeps no longer accumulate: each job drops itself from the tracking list on completion, a sweep already scanning a storage directory is skipped rather than duplicated, and a tree another walker owns is left to it. Added visitFileFailed/DirectoryNotEmptyException handling so a concurrently vanishing entry no longer aborts the rest of the walk. - remove() now runs `worktree prune --expire now` on every successful arm before `branch -D`. The prune-only arm previously deleted the branch while git still had the worktree registered, so the delete failed (logged only) and the trailing prune used the default three-month expiry, leaving both the branch and the registration behind.
marius-kilocode
approved these changes
Sep 8, 2026
This was referenced Sep 8, 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.
Issue
Fixes #
No tracked issue — found and fixed while diagnosing a user-reported JetBrains Agent Manager worktree deletion failure from local logs.
Context
Deleting an Agent Manager worktree with a large
node_modulesblocked theremoveRPC for 25–30s, becausegit worktree remove --forceruns the recursive filesystem delete synchronously. While that delete was in flight, every other polling loop (stats,dirty,prStatus,branchStatus, the gh probe) kept spawninggit/ghprocesses against the directory mid-delete, and one directory disappearing mid-command threwIllegalStateException: ... Unable to read current working directory, which cancelled every other worktree's result through the sharedparallel()coroutine scope — so a single slow delete broke stats/dirty/PR badges for the whole Agent Manager list, which is what the user experienced as "deletion failed."Six real deletions were traced end-to-end in the log: all of them eventually succeeded, but each blocked 18–31s and produced a wave of unrelated-looking crashes for the surviving worktrees during that window.
Implementation
remove()now atomically renames the worktree to a.kilo-delete-<uuid>sibling (newWorktreeTrashservice, matching the VS Code extension'sWorktreeManagerconvention — same prefix, so either client sweeps the other's orphans), runsgit worktree prune --expire now, deletes the branch, and returns success immediately. The actual recursive delete runs in the background onWorktreeTrash's own coroutine scope. A worktree that is locked withoutforceskips staging entirely and falls through to the existing synchronousgit worktree remove --force— a filesystem rename can't see or honor a git lock, and renaming a directory git still considers locked would leave its metadata permanently stuck as prunable-but-locked.sync/list/stats/dirty/prStatus/branchStatus/the gh probe) now checksWorktreeTrash's doomed-path registry and skips a worktree that's mid-removal, without caching the empty answer.stats/dirty's per-item work is now wrapped so one worktree throwing (e.g. its directory vanishing mid-command) returns a neutral entry instead of cancelling every sibling result viaparallel()'s sharedcoroutineScope.badDir()now also recognizes git's own"Unable to read current working directory"message (from the log) and classifies it as an expected race (INFO) rather than a real fault (WARN).CmdOutnow carries atimeoutflag, the fallbackgit worktree remove --forcegets a 10-minute budget instead of the 30s default used for cheap queries, and a killed process reports "timed out" instead of a blank error.create/import/remove/rename/adopt/reorder/session-list) are now serialized per repository with aMutex, so concurrent calls can't interleaveworktree list/remove/pruneor race the.kilo/jetbrains.jsonread-modify-write. Read paths stay unlocked.moveToWorktree's rollback deliberately isn't nested under the same lock as its create, sinceMutexisn't reentrant.remove()now logs with elapsed time and the removal mode (prune-only/rename/gitfallback), so a slow delete is distinguishable from a hung one.Screenshots / Video
N/A — backend/logging change, no UI change.
How to Test
Manual/local verification
./gradlew testfor the wholepackages/kilo-jetbrainsplugin (backend, frontend, shared) — all green../gradlew typecheckfor the plugin — passes.kilo.log/idea.logexcerpts against the new code paths to confirm the fix addresses the exact observed exceptions and blocking durations.Reviewer test steps
bun install(or similar) in it so it has a largenode_modules.kilo.logshould showworktree removed: ... mode=rename ms=<small>.Git comparison failedstack traces in the log..kilo/worktrees/ends up with no leftover.kilo-delete-*directories.Blocked checks and substitute verification
runIdeSplitMode) for this change; substituted with the Gradle unit/integration test suite (984 backend tests + frontend tests, all realgitrepos in temp directories, no mocks) covering the rename/prune fast path, the locked-without-force fallback, per-item failure isolation via a corrupted index, doomed-path skip behavior for stats/dirty/branchStatus, and four concurrent removes leaving consistent worktree-list/state-file output.Checklist
packages/kilo-jetbrains/is not part of the bun changesets system (.changeset/config.json'sfixedgroup only coverskilo-code/@kilocode/cli); JetBrains has its own changelog process via therelease-jetbrainsrelease flow.Get in Touch