Skip to content

fix(rules): terminate findProjectRoot walk at non-cwd drive roots - #19

Open
lavi-kj wants to merge 1 commit into
code-yeongyu:mainfrom
lavi-kj:fix/find-project-root-cross-drive-walk
Open

fix(rules): terminate findProjectRoot walk at non-cwd drive roots#19
lavi-kj wants to merge 1 commit into
code-yeongyu:mainfrom
lavi-kj:fix/find-project-root-cross-drive-walk

Conversation

@lavi-kj

@lavi-kj lavi-kj commented Jul 28, 2026

Copy link
Copy Markdown

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 null instantly).
  • Verified live on Windows 11 (cwd on E:, temp on C:): 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-paths tests); 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 on resolve("/"), covering cross-drive and UNC roots.
    • Add a Windows multi-drive regression test that returns null instead of hanging.

Written for commit 2fc5bc4. Summary will update on new commits.

Review in cubic

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).
@lavi-kj
lavi-kj requested a review from code-yeongyu as a code owner July 28, 2026 11:11
code-yeongyu pushed a commit to code-yeongyu/senpi that referenced this pull request Jul 30, 2026
…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
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.

1 participant