Skip to content

fix(helpers): accept Windows absolute paths in normalizeDirectory#6

Open
samuelgudi wants to merge 1 commit intoAlaeddineMessadi:mainfrom
samuelgudi:fix/windows-absolute-path-validation
Open

fix(helpers): accept Windows absolute paths in normalizeDirectory#6
samuelgudi wants to merge 1 commit intoAlaeddineMessadi:mainfrom
samuelgudi:fix/windows-absolute-path-validation

Conversation

@samuelgudi
Copy link
Copy Markdown

@samuelgudi samuelgudi commented Apr 15, 2026

Closes #5.

Summary

normalizeDirectory checked the resolved path with normalized.startsWith(\"/\"), which is only correct on POSIX. On Windows, path.resolve returns drive-letter paths like C:\Users\me\project, so every Windows absolute path was rejected and the directory parameter was effectively unusable for Windows MCP clients (all 79 tools affected).

This PR switches the check to the platform-aware path.isAbsolute from node:path. path.resolve already guarantees an absolute path on every supported platform, so the new check is equivalent on POSIX (behavior unchanged) and correct on Windows.

Changes

  • src/helpers.ts
    • Import isAbsolute from node:path.
    • Replace normalized.startsWith(\"/\") with !isAbsolute(normalized).
    • Extend the error message to include a Windows example alongside the POSIX one.
    • Update the JSDoc to describe the POSIX + Windows support.
  • tests/helpers.test.ts
    • New test: accepts the process working directory on any platform — a platform-agnostic regression that passes process.cwd() through normalizeDirectory. With the old code this test fails on Windows (cwd is C:\...) and passes on Linux; with the new code it passes on both.
    • New test (skipped on non-Windows via it.runIf): accepts Windows drive-letter absolute paths — asserts that a real Windows drive-letter path is accepted and round-trips through the function.

No runtime dependencies added. No public API change. POSIX semantics unchanged.

Test results

Run on Windows 11 with Node 22, npm test:

  • Baseline (upstream/main): 20 failing / 300 passing / 320 total
  • This branch: 13 failing / 309 passing / 322 total (2 new tests added)

The fix itself turns 7 previously-failing Windows tests green (the ones exercising normalizeDirectory via the tool handlers with Linux-style paths that resolve to the current drive but then hit the absolute-path check incorrectly). The remaining 13 failures on Windows are pre-existing and unrelated to this fix — they hardcode Linux paths like /tmp into string comparisons (e.g. expect(result).toBe(\"/tmp\")), which can't succeed on Windows where resolve(\"/tmp\") returns X:\tmp. Happy to file a follow-up PR making those tests platform-agnostic if you want, but I kept this PR scoped to the actual bug.

On Linux, the full suite is unchanged by these changes — the new platform-agnostic test passes (cwd is absolute on Linux too), and the Windows-only test is skipped via it.runIf(process.platform === \"win32\").

Workaround that this replaces

Before this fix, the only way to make opencode-mcp usable on Windows was to omit the directory parameter entirely and rely on the cwd of the process that spawned opencode serve. That works for single-project setups but defeats the point of the per-request directory header in multi-project setups.

Conventional Commits

fix(helpers): accept Windows absolute paths in normalizeDirectory

Summary by CodeRabbit

  • Bug Fixes
    • Path normalization now correctly validates and handles absolute paths on all platforms, including Windows drive-letter format paths and standard POSIX paths, with improved error messages that cover platform-specific path examples.
  • Tests
    • Added regression tests to verify platform-specific absolute path validation functions correctly on both Windows and POSIX systems.

normalizeDirectory previously required the resolved path to start with
"/", which is only correct on POSIX platforms. On Windows, path.resolve
produces drive-letter paths like "C:\Users\me\project" which failed
the startsWith("/") check and were incorrectly rejected as "not an
absolute path", making every tool that accepts the `directory` parameter
unusable for Windows clients.

Switch the check to path.isAbsolute from node:path, which is
platform-aware and recognizes both POSIX ("/home/user/project") and
Windows ("C:\Users\me\project", "\\server\share") absolute paths.
The check is performed on the result of path.resolve, so relative input
paths are still normalized against process.cwd() as before — behavior on
POSIX platforms is unchanged.

Also update the error message for invalid directories to include both a
POSIX and a Windows example, and add two regression tests: one that
accepts process.cwd() on any platform, and one (skipped on non-Windows
runners) that confirms a Windows drive-letter path is accepted.

On a Windows test run the upstream suite went from 20 failing tests to
13 — the 13 remaining failures are unrelated Linux-only assumptions in
other tests (hardcoded /tmp, /home paths) and are out of scope here.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 15, 2026

📝 Walkthrough

Walkthrough

The changes update normalizeDirectory() to use platform-aware path validation instead of POSIX-only checks, replacing the leading / check with isAbsolute(). Documentation and error messages are updated to reflect support for both POSIX and Windows absolute paths, including UNC paths. Tests are added to verify the behavior.

Changes

Cohort / File(s) Summary
Platform-aware path validation
src/helpers.ts, tests/helpers.test.ts
Replaced POSIX-only validation with path.isAbsolute() to support Windows and UNC paths; updated documentation and error messages with platform-specific examples; added regression tests for process.cwd() and Windows drive-letter paths.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

  • Issue #5: The PR directly implements platform-aware path validation using isAbsolute() instead of POSIX-only checks, replacing the leading / validation as described in the issue.

Poem

🐰 Twitches whiskers with cross-platform cheer
Windows and POSIX, now both are clear!
No more / assumptions in sight,
isAbsolute() makes paths right,
Hopping through all platforms with delight! 🛤️✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main fix: enabling normalizeDirectory to accept Windows absolute paths, which was the core bug being addressed.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (3)
tests/helpers.test.ts (2)

931-936: Strengthen the regression assertion.

toBeDefined() can pass even with a wrong normalization result. Assert the exact normalized value to make this test more diagnostic.

✅ Stronger assertion
+import { resolve } from "node:path";
...
   it("accepts the process working directory on any platform", () => {
     // Platform-agnostic regression: process.cwd() is always absolute for the
     // current platform, so normalizeDirectory must accept it regardless of OS.
-    const result = normalizeDirectory(process.cwd());
-    expect(result).toBeDefined();
+    const cwd = process.cwd();
+    const result = normalizeDirectory(cwd);
+    expect(result).toBe(resolve(cwd));
   });
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/helpers.test.ts` around lines 931 - 936, The test currently only checks
normalizeDirectory(process.cwd()) is defined; replace the weak toBeDefined()
assertion with a deterministic equality check using Node's path utilities:
compute the expected value with path.resolve(process.cwd()) (or
path.normalize/process-specific normalization consistent with how
normalizeDirectory implements it) and assert expect(result).toBe(expected). Also
ensure the test file imports Node's path (e.g., const path = require("path") or
import path from "path") so you can calculate expected via
path.resolve(process.cwd()) to make the regression assertion exact.

943-951: Make the Windows test less environment-sensitive.

This currently assumes process.cwd() is always drive-letter formatted. Some Windows environments can use UNC/prefixed cwd paths, which makes this flaky.

🧪 More robust drive-letter test setup
   it.runIf(process.platform === "win32")(
     "accepts Windows drive-letter absolute paths",
     () => {
-      const cwd = process.cwd(); // e.g. "C:\\Users\\runner\\work\\..."
-      expect(cwd).toMatch(/^[A-Za-z]:\\/);
-      const result = normalizeDirectory(cwd);
+      const cwd = process.cwd();
+      const drivePath = /^[A-Za-z]:\\/.test(cwd)
+        ? cwd
+        : `${process.env.SystemDrive ?? "C:"}\\`;
+      const result = normalizeDirectory(drivePath);
       expect(result).toBeDefined();
       expect(result).toMatch(/^[A-Za-z]:\\/);
     },
   );
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/helpers.test.ts` around lines 943 - 951, The test "accepts Windows
drive-letter absolute paths" is flaky because it uses process.cwd() which may be
a UNC or prefixed path; instead make the test environment-independent by
supplying a deterministic drive-letter path or by mocking process.cwd() to
return one. Update the test around normalizeDirectory to either set const cwd =
"C:\\temp\\some\\path" (or build one from a single-letter drive) or spy on
process.cwd() to return that drive-letter path, then call
normalizeDirectory(cwd) and assert the drive-letter regex; ensure the test name
and use of normalizeDirectory remain unchanged.
src/helpers.ts (1)

99-112: isAbsolute(normalized) is effectively unreachable after resolve().

Because resolve() already returns an absolute path, this branch is mostly dead code and the “not an absolute path” error path won’t realistically execute. Consider either validating the raw input before resolving (if absolute-only is intended) or dropping this guard to avoid misleading logic.

♻️ Optional refactor (if absolute-only input is intended)
-  const normalized = resolve(directory);
-
-  // Defensive check: `resolve` guarantees an absolute path on every
-  // supported platform, but we verify via the platform-aware `isAbsolute`
-  // so callers get a clear error if that assumption is ever violated.
-  if (!isAbsolute(normalized)) {
+  if (!isAbsolute(directory)) {
     throw new Error(
       `Invalid directory: "${directory}" is not an absolute path. ` +
         `Provide a full path like "/home/user/my-project" (POSIX) or ` +
         `"C:\\\\Users\\\\me\\\\my-project" (Windows).`,
     );
   }
+  const normalized = resolve(directory);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/helpers.ts` around lines 99 - 112, The isAbsolute check after calling
resolve() is redundant because resolve(directory) always returns an absolute
path; update the code by either moving the absolute-path validation to before
calling resolve (validate directory with isAbsolute(directory) and throw there
if you require callers to supply an absolute path) or remove the post-resolve
guard entirely so you don't have unreachable logic; look for the symbols
resolve, isAbsolute, normalized, and directory to make the change and keep error
messaging consistent with whichever approach you pick.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@src/helpers.ts`:
- Around line 99-112: The isAbsolute check after calling resolve() is redundant
because resolve(directory) always returns an absolute path; update the code by
either moving the absolute-path validation to before calling resolve (validate
directory with isAbsolute(directory) and throw there if you require callers to
supply an absolute path) or remove the post-resolve guard entirely so you don't
have unreachable logic; look for the symbols resolve, isAbsolute, normalized,
and directory to make the change and keep error messaging consistent with
whichever approach you pick.

In `@tests/helpers.test.ts`:
- Around line 931-936: The test currently only checks
normalizeDirectory(process.cwd()) is defined; replace the weak toBeDefined()
assertion with a deterministic equality check using Node's path utilities:
compute the expected value with path.resolve(process.cwd()) (or
path.normalize/process-specific normalization consistent with how
normalizeDirectory implements it) and assert expect(result).toBe(expected). Also
ensure the test file imports Node's path (e.g., const path = require("path") or
import path from "path") so you can calculate expected via
path.resolve(process.cwd()) to make the regression assertion exact.
- Around line 943-951: The test "accepts Windows drive-letter absolute paths" is
flaky because it uses process.cwd() which may be a UNC or prefixed path; instead
make the test environment-independent by supplying a deterministic drive-letter
path or by mocking process.cwd() to return one. Update the test around
normalizeDirectory to either set const cwd = "C:\\temp\\some\\path" (or build
one from a single-letter drive) or spy on process.cwd() to return that
drive-letter path, then call normalizeDirectory(cwd) and assert the drive-letter
regex; ensure the test name and use of normalizeDirectory remain unchanged.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 6ba3631b-3176-4ae9-8e43-5fa309af2837

📥 Commits

Reviewing files that changed from the base of the PR and between 8c6f5c1 and 34cb64f.

📒 Files selected for processing (2)
  • src/helpers.ts
  • tests/helpers.test.ts

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.

normalizeDirectory rejects Windows absolute paths (C:\..., \server\share)

1 participant