fix(coding-agent): terminate vendored rules findProjectRoot walk at non-cwd drive roots

Merged
LA
3mo ago
+46-2

Summary

On Windows, every read/edit/write tool call targeting a file on a different drive than the session cwd freezes the agent session permanently: the vendored pi-rules findProjectRoot walks parent directories looking for project markers and never terminates, spinning one core at 100% inside the rules extension's tool_result handler.

Root cause

findProjectRoot terminates its walk only when currentDirectory === resolve("/"), but resolve("/") is cwd's drive root (e.g. E:\), while dirname() 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::

  • Session JSONL audit: 6/6 tool calls targeting C: paths (temp paste images, files under the user profile) never produced a toolResult; 26/26 same-drive calls succeeded.
  • A headless senpi --print repro burned 53.6s CPU in 60s before producing any output.
  • 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). The existing resolve("/") 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's changes.md per 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 returns null instantly)

Upstream

The same fix was submitted to the vendored source: https://github.com/code-yeongyu/pi-rules/pull/19 — the changes.md entry notes this adaptation can be dropped once a fixed pi-rules release is re-vendored.

Validation

  • New regression test passes on Windows 11 (cwd on E:, temp on C: — real cross-drive), ~5ms.
  • Root npm run check clean (biome + tsgo + lock/import checks).
  • The identical fix in the pi-rules repo passes its full project-root suite 12/12 with no new failures.

Summary by cubic

Fixes a Windows-only infinite loop in findProjectRoot when accessing files on a different drive than the cwd. The walk now terminates when dirname() stops progressing and returns null for cross-drive and UNC paths without markers.

  • Bug Fixes
    • Updated packages/coding-agent/src/core/extensions/builtin/rules/rules/project-root.ts to stop the walk when dirname() no longer changes the path (keeps the resolve("/") early exit).
    • Added packages/coding-agent/test/suite/regressions/0000-rules-find-project-root-cross-drive.test.ts to exercise the cross-drive case in CI and assert it returns null instead of hanging.
    • Documented the vendored change in packages/coding-agent/src/core/extensions/builtin/rules/changes.md; upstream fix submitted: https://github.com/code-yeongyu/pi-rules/pull/19.