fix(rules): terminate findProjectRoot walk at non-cwd drive roots
Summary
On Windows, findProjectRoot loops 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
const filesystemRoot = resolve("/"); // -> cwd's drive root, e.g. "E:\"
while (true) {
// ... marker checks ...
if (currentDirectory === filesystemRoot) return null; // "C:\" === "E:\" never holds
currentDirectory = dirname(currentDirectory); // dirname("C:\") === "C:\"
}
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:, every read/edit/write tool call targeting a file on C: (temp screenshots, files under the user profile, etc.) froze the session permanently:
- 6/6 cross-drive tool calls hung; 26/26 same-drive calls succeeded (session JSONL audit)
- Hung process burned one full core (53.6s CPU in 60s)
- V8 inspector stack of the hung process:
#0 findProjectRoot (rules/rules/project-root.js)
#1 fingerprintDynamicTargets (rules/rules/engine.js)
#2 tool_result handler (rules/index.js)
#3 emitToolResult (extension runner)
Because the loop is synchronous, the abort signal is never processed: esc cannot interrupt, and users must kill the process.
Fix
Stop the walk when dirname() stops progressing (parentDirectory === currentDirectory). This preserves the existing resolve("/") early-exit and covers cross-drive roots and UNC share roots uniformly.
Tests
- New regression test exercising the real cross-drive scenario on multi-drive Windows machines (no-ops on POSIX and single-drive Windows where the scenario cannot occur; pre-fix it hangs, post-fix it returns
nullinstantly). - Verified live on Windows 11 (cwd on
E:, temp onC:): the new test passes in ~1ms with the fix. - Full suite: 244 tests, same 10 failures as the base commit (pre-existing Windows symlink-privilege environment issues in
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.md entry will follow.
Summary by cubic
Fixes a Windows-only infinite loop in findProjectRoot that froze the agent on cross‑drive or UNC paths without markers. The walk now terminates correctly, preventing 100% CPU hangs on these tool calls.
- Bug Fixes
- Stop traversal when
dirname()stops changing the path instead of relying only onresolve("/"), covering cross-drive and UNC roots. - Add a Windows multi-drive regression test that returns
nullinstead of hanging.
- Stop traversal when