fix(windows): canonicalize rooted-but-driveless paths in permission helpers#431
Conversation
…probe assertExternalDirectory and read both call AppFileSystem.normalizePath, but on Windows that fell through to path.resolve which silently bound rooted-but- driveless paths (/users/runner/...) to cwd's drive. When the file lived on a different drive (typical on GitHub runners: temp on C: vs workspace on D:) the resulting permission glob used the wrong drive. Probe each known drive root for an existing path before falling back to win32.resolve. Move the helper into core/filesystem and delegate from opencode/util/filesystem so both paths share a single implementation. Closes #427
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: Path: .coderabbit.yaml Review profile: CHILL Plan: Pro Plus Run ID: 📒 Files selected for processing (1)
✅ Files skipped from review due to trivial changes (1)
📝 WalkthroughWalkthroughCore filesystem Windows normalization now probes known drive roots for rooted-but-driveless absolute paths before falling back to win32.resolve. The opencode package delegates its path-normalization utilities to core's AppFileSystem. New tests cover Git Bash, Cygwin, WSL, rooted-but-driveless, fallback, and glob-preservation cases. ChangesWindows Path Normalization Consolidation
Estimated code review effort🎯 4 (Complex) | ⏱️ ~50 minutes Possibly related issues
Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Review rate limit: 9/10 reviews remaining, refill in 6 minutes. Comment |
There was a problem hiding this comment.
Code Review
This pull request refactors filesystem path normalization by centralizing the logic within the core package and reusing it across the codebase. It specifically improves Windows path handling for rooted but driveless paths by probing available drive roots. Feedback was provided regarding the performance of the windowsDriveRoots function, suggesting that caching the drive list would be more efficient for bulk operations.
Drive probe only repairs existing rooted-driveless paths; surface that contract as a test asserting non-existent targets still bind to cwd's drive. Cover Git Bash /c/, Cygwin /cygdrive/c/, WSL /mnt/c/ entry points plus the bare /users/... shape that motivated the fix. Tests are no-ops on macOS/Linux where normalizePath is the identity.
|
windows-advisory dispatch run 25311230257 finished green — all 5 shards (changes, app, desktop, config-project, session, server-tools) passed on this branch. Server-tools was the last shard still red on dev (held by the two #427 tests); now confirmed unblocked. Reviewer-recommended merge gate satisfied:
|
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
packages/core/test/filesystem/normalize-path.test.ts (1)
17-33: 🏗️ Heavy liftForce this case to cross a drive boundary.
On runners where
os.tmpdir()shares a drive withprocess.cwd(), this can still pass even if the new drive-probing logic is broken, because the legacy fallback can produce the same result. Consider pinning the fixture to a different drive or mocking the drive-root probe so this actually exercises the regression path.🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@packages/core/test/filesystem/normalize-path.test.ts` around lines 17 - 33, The test currently can pass on same-drive runners; modify the test for "normalizePath probes drive roots for rooted-but-driveless paths to existing files" to force crossing a drive boundary so the drive-probing branch in AppFileSystem.normalizePath is exercised: either create the temporary fixture on a different drive than process.cwd() (e.g., build the tmpdir path by prepending a different drive letter) or mock/stub the internal drive-root probe used by AppFileSystem.normalizePath (or the helper that enumerates drive roots) so the function must probe other drives; update the test to assert the normalized path equals the file only when the probe path is exercised and clean up the fixture in the finally block.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/core/test/filesystem/normalize-path.test.ts`:
- Around line 111-120: The test currently only asserts the normalized path has a
drive letter; change it to assert the exact expected fallback path by
constructing the expected string from the current working directory's drive and
the original driveless path. Compute the expected drive prefix from
process.cwd() (first two chars like "C:"), append the driveless path with its
leading slash removed and with appropriate Windows separators (use path.join or
equivalent) and then replace the toMatch assertion with a strict
equality/assertion comparing AppFileSystem.normalizePath(driveless) to that
expected path so the test fails if the wrong drive is chosen.
---
Nitpick comments:
In `@packages/core/test/filesystem/normalize-path.test.ts`:
- Around line 17-33: The test currently can pass on same-drive runners; modify
the test for "normalizePath probes drive roots for rooted-but-driveless paths to
existing files" to force crossing a drive boundary so the drive-probing branch
in AppFileSystem.normalizePath is exercised: either create the temporary fixture
on a different drive than process.cwd() (e.g., build the tmpdir path by
prepending a different drive letter) or mock/stub the internal drive-root probe
used by AppFileSystem.normalizePath (or the helper that enumerates drive roots)
so the function must probe other drives; update the test to assert the
normalized path equals the file only when the probe path is exercised and clean
up the fixture in the finally block.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: da36fde7-1cc8-42f9-98c8-b30bfab04638
📒 Files selected for processing (2)
packages/core/src/filesystem.tspackages/core/test/filesystem/normalize-path.test.ts
🚧 Files skipped from review as they are similar to previous changes (1)
- packages/core/src/filesystem.ts
toMatch(/^[A-Za-z]:/) accepted any drive letter, so the fallback could land on the wrong drive without failing the test. Construct the expected cwd-drive answer and assert exact equality.
Summary
assertExternalDirectoryandreadboth rely onAppFileSystem.normalizePathto canonicalize a user-supplied path before deriving permission globs. On Windows that fell through topath.resolve, which silently bound rooted-but-driveless paths (e.g./users/runner/...) to the cwd's drive. When the file lived on a different drive (typical on GitHub runners: temp onC:vs workspace onD:), the resulting glob andfs.openpath pointed at the wrong drive.Probe each known drive root for an existing path before falling back to
win32.resolve. The helper now lives in@opencode-ai/core/filesystemsoAppFileSystem.normalizePathandFilesystem.normalizePathshare one implementation; opencode'sFilesystemthin-wraps it.Why
#427 carved two real Windows runtime bugs out of the windows-advisory failures (the rest were test-fixture issues, fixed in #429). Without this change
tool.assertExternalDirectory > normalizes Windows path variants to one globandtool.read external_directory permission > normalizes read permission paths on Windowsboth fail because the permission glob uses the cwd's drive instead of the file's actual drive.Related Issue
Fixes the rooted-but-driveless cases in #427. UNC
\\?\prefixes, ambiguous multi-drive matches, and write-target (non-existing path) handling are deliberately out of scope — kept open for a follow-up if windows-advisory surfaces them.Human Review Status
Pending.
Review Focus
normalizeWindowsAbsolutePathandwindowsDriveRoots. The probe runs syncexistsSyncper drive; that adds I/O to a previously pure path operation, but only on Windows and only for rooted-but-driveless inputs (a narrow code path).packages/opencode/src/util/filesystem.ts—Filesystem.normalizePath/resolve/etc. are now re-exports of the core helpers. Existing callers (Filesystem.contains,Filesystem.overlaps, LSP, project/instance, plugin/install) keep working through the namespace.packages/core/test/filesystem/normalize-path.test.tscovers Git Bash, Cygwin, WSL, and bare driveless variants plus an explicit guard that non-existing rooted-driveless paths still bind to cwd's drive.Risk Notes
existsSync. On a hung network drive this could block; in practice CI runners only have C/D so the cost is bounded.win32.resolve, which still binds to cwd's drive. This matches prior behavior; only the existing-path case is improved.windowsDriveRoots()per call (no caching). Hot paths that previously assumednormalizePathwas pure (LSP, ripgrep) now do up to 26existsSyncsyscalls per Windows call. Cache invalidation is a follow-up.How To Verify
Screenshots or Recordings
None — no UI changes.
Checklist
Summary by CodeRabbit
Bug Fixes
Refactor
Tests