Repository navigation
fix(rules): terminate findProjectRoot walk at non-cwd drive roots - #19
code-yeongyu merged 1 commit into
Conversation
…on-cwd drive roots
On Windows, resolve("/") is the cwd drive's root, but dirname() of
another drive's root returns itself. For targets on a different drive
than cwd (or UNC shares), the vendored pi-rules findProjectRoot marker
walk never reaches filesystemRoot and spins forever - a synchronous
infinite loop in the rules extension's tool_result handler that freezes
the agent session (100% CPU, abort signal never processed) on every
read/edit/write call targeting such paths.
Stop the walk when dirname() stops progressing, document the adaptation
in the extension's changes.md, and add a cross-drive regression test.
Upstream fix for the vendored source: code-yeongyu/pi-rules#19
On Windows, resolve("/") is the cwd drive's root, but dirname() of
another drive's root returns itself. For targets on a different drive
than cwd (or UNC shares), the marker walk never reaches
filesystemRoot and spins forever - a synchronous infinite loop that
freezes the host agent's event loop at 100% CPU on every
read/edit/write tool_result for such paths.
Stop the walk when dirname() stops progressing, and add a cross-drive
regression test (real scenario on multi-drive Windows machines).
2fc5bc4 to
95e16e6
Compare
code-yeongyu
left a comment
There was a problem hiding this comment.
The dirname-stall guard in findProjectRoot is correct and matches the walk already used in getWalkDirectories / widenToRepositoryRoot. On Windows, resolve("/") is the cwd drive root, so a target on another drive (or a UNC share) never equals that sentinel and the old loop spun forever. Rebased onto current main (conflicts were changelog + test imports only).
Nit on the regression test: it returns early on non-Windows instead of skipIf, so CI never exercises it. Acceptable for a Windows-only bug; the production guard is still the right fix.
Local check + 278 unit tests + 46 integration tests passed after rebase. Merging.
| // Cross-drive / UNC walk: dirname() of a drive root returns the root itself, | ||
| // and a root on a different drive than cwd never equals resolve("/"), so the | ||
| // walk would spin forever without this guard. | ||
| if (parentDirectory === currentDirectory) { |
There was a problem hiding this comment.
Correct termination: dirname() of a drive root (and of a UNC share root) returns itself, so this is the portable stop condition. filesystemRoot === resolve("/") is not enough when cwd is on a different drive than startPath.
| it("#given startPath on a different drive than cwd (Windows cross-drive) #when finding root #then terminates with null instead of looping forever", () => { | ||
| // Cross-drive paths only exist on Windows; this scenario cannot occur elsewhere. | ||
| if (process.platform !== "win32") { | ||
| return; |
There was a problem hiding this comment.
This body is a silent no-op on linux/mac CI (bare return rather than it.skipIf). Fine as a Windows-only scenario; not a merge blocker because the guard is a one-line mechanical stop and is already covered in spirit by the no-marker walk returning null.
Summary
On Windows,
findProjectRootloops forever when the target path is on a different drive than the process cwd (or on a UNC share) and no project marker exists up-tree. This is a synchronous infinite loop, so it freezes the host agent's entire event loop at 100% CPU.Root cause
The walk terminates only when it reaches
resolve("/"), which is cwd's drive root. For a target on another drive,dirname()of that drive's root returns the root itself, so the equality never holds and the loop never exits.Real-world impact (how this was found)
Discovered while debugging frozen sessions in @code-yeongyu/senpi, which vendors pi-rules as a builtin extension. On a Windows machine with the workspace on
E:, everyread/edit/writetool call targeting a file onC:(temp screenshots, files under the user profile, etc.) froze the session permanently:Because the loop is synchronous, the abort signal is never processed:
esccannot interrupt, and users must kill the process.Fix
Stop the walk when
dirname()stops progressing (parentDirectory === currentDirectory). This preserves the existingresolve("/")early-exit and covers cross-drive roots and UNC share roots uniformly.Tests
nullinstantly).E:, temp onC:): the new test passes in ~1ms with the fix.scanner/finder/engine/extension-registration/tool-pathstests); no new failures.Downstream
senpi vendors this package (
packages/coding-agent/src/core/extensions/builtin/rules, MANUAL_PACKAGES). A companion senpi PR applying the same guard to the vendored copy +changes.mdentry will follow.Summary by cubic
Fixes a Windows-only infinite loop in
findProjectRootthat froze the agent on cross‑drive or UNC paths without markers. The walk now terminates correctly, preventing 100% CPU hangs on these tool calls.dirname()stops changing the path instead of relying only onresolve("/"), covering cross-drive and UNC roots.nullinstead of hanging.Written for commit 95e16e6. Summary will update on new commits.