Repository navigation
fix(coding-agent): terminate vendored rules findProjectRoot walk at non-cwd drive roots - #432
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
code-yeongyu
left a comment
There was a problem hiding this comment.
VERDICT: MERGE_OK
The production fix is small, correctly placed, and directly addresses the reported synchronous infinite loop. findProjectRoot now treats a dirname() fixpoint as a filesystem root, so a non-cwd Windows drive root or UNC root terminates instead of spinning forever (packages/coding-agent/src/core/extensions/builtin/rules/rules/project-root.ts:28-35). The existing cwd-root check remains first (project-root.ts:24-26), so POSIX / behavior is unchanged. The vendored divergence and upstream removal condition are also documented clearly (packages/coding-agent/src/core/extensions/builtin/rules/changes.md:10).
I found one serious test-quality defect during review: the original regression returned immediately on non-Windows and on single-drive Windows, so its body ran in none of the standard GitHub-hosted CI environments. Mutation-checking confirmed that the original test still passed after reverting the production guard. I fixed and pushed this as 558123aa3:
- The test now simulates an
E:\cwd root and aC:\workspace\plain\nestedtarget with the realpath.win32operations (packages/coding-agent/test/suite/regressions/0000-rules-find-project-root-cross-drive.test.ts:5-23). - It exercises the real
findProjectRootimplementation on every platform and asserts the Windows drive-root fixpoint plus the expectednullresult (0000-rules-find-project-root-cross-drive.test.ts:25-35). - With the production guard temporarily reverted, this deterministic test entered the old synchronous loop and hit the bounded 5-second test-process timeout. Restoring the guard made it pass in 263 ms.
Verification:
npm test -- test/suite/regressions/0000-rules-find-project-root-cross-drive.test.tsfrompackages/coding-agent: 1 file / 1 test passed- Mutation check with
project-root.tsguard reverted: timed out after 5 seconds, reproducing the infinite loop; source was restored before commit - POSIX invariant:
path.posix.dirname("/") === "/" - Root
npm run check: passed (Biome, dependency/import/lock checks, tsgo, browser smoke, web UI check) - Branch is current with
origin/main(git rev-list --left-right --count HEAD...origin/main=3 0); no additional main merge was required
No remaining blocking findings. The core change is appropriately minimal, the vendored adaptation is documented, and the regression now genuinely protects the behavior in CI.
Summary
On Windows, every
read/edit/writetool call targeting a file on a different drive than the session cwd freezes the agent session permanently: the vendored pi-rulesfindProjectRootwalks parent directories looking for project markers and never terminates, spinning one core at 100% inside the rules extension'stool_resulthandler.Root cause
findProjectRootterminates its walk only whencurrentDirectory === resolve("/"), butresolve("/")is cwd's drive root (e.g.E:\), whiledirname()of another drive's root returns that root itself (e.g.C:\). For cross-drive (or UNC) targets with no marker up-tree, the exit condition never holds and the synchronous loop never ends.How this was found (evidence)
Reported as repeatedly freezing interactive sessions. Reproduced and dissected on Windows 11 with senpi 2026.7.26, workspace on
E::C:paths (temp paste images, files under the user profile) never produced atoolResult; 26/26 same-drive calls succeeded.senpi --printrepro burned 53.6s CPU in 60s before producing any output.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). The existingresolve("/")early-exit is preserved; cross-drive and UNC roots are covered uniformly.packages/coding-agent/src/core/extensions/builtin/rules/rules/project-root.ts— the guard (vendored file; adaptation documented in the extension'schanges.mdper the contract)test/suite/regressions/0000-rules-find-project-root-cross-drive.test.ts— regression test exercising the real cross-drive scenario on multi-drive Windows machines (no-ops where the scenario cannot occur; pre-fix it hangs, post-fix returnsnullinstantly)Upstream
The same fix was submitted to the vendored source: code-yeongyu/pi-rules#19 — the
changes.mdentry notes this adaptation can be dropped once a fixed pi-rules release is re-vendored.Validation
E:, temp onC:— real cross-drive), ~5ms.npm run checkclean (biome + tsgo + lock/import checks).Summary by cubic
Fixes a Windows-only infinite loop in
findProjectRootwhen accessing files on a different drive than the cwd. The walk now terminates whendirname()stops progressing and returnsnullfor cross-drive and UNC paths without markers.packages/coding-agent/src/core/extensions/builtin/rules/rules/project-root.tsto stop the walk whendirname()no longer changes the path (keeps theresolve("/")early exit).packages/coding-agent/test/suite/regressions/0000-rules-find-project-root-cross-drive.test.tsto exercise the cross-drive case in CI and assert it returnsnullinstead of hanging.packages/coding-agent/src/core/extensions/builtin/rules/changes.md; upstream fix submitted: fix(rules): terminate findProjectRoot walk at non-cwd drive roots pi-rules#19.Written for commit 558123a. Summary will update on new commits.