Skip to content

fix(tool): normalize Windows drive aliases for bash paths#2635

Closed
roboomp wants to merge 3 commits into
mainfrom
farm/f1b98c52/fix-msys-drive-paths
Closed

fix(tool): normalize Windows drive aliases for bash paths#2635
roboomp wants to merge 3 commits into
mainfrom
farm/f1b98c52/fix-msys-drive-paths

Conversation

@roboomp

@roboomp roboomp commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator

Repro

On the current branch, the tool-layer path resolver leaves MSYS drive aliases unchanged: bun --print 'import { resolveToCwd } from "@oh-my-pi/pi-coding-agent/tools/path-utils"; resolveToCwd("/d/project", "D:/project")' prints /d/project, so bash cwd validation reaches fs.stat with the wrong Windows path. The same missing normalization exists in brush filesystem resolution used by cd and stat-backed builtins.

Cause

packages/coding-agent/src/tools/path-utils.ts only expanded ~ and then trusted platform absolute-path detection, which does not translate /d/... or /mnt/d/... before cwd validation. In brush, crates/brush-core-vendored/src/shell/fs.rs passed shell paths directly into std::fs::metadata/OpenOptions; only glob pattern expansion had Windows drive-root awareness, so ls and cd disagreed.

Fix

  • Normalize /c/..., /d/..., and /mnt/<drive>/... aliases to native Windows drive paths before coding-agent tool path resolution.
  • Add a brush-core shell path normalization hook used by absolute_path, covering cd, redirects, and stat-style builtins that resolve through the shell.
  • Add focused TypeScript and Rust regression tests for MSYS and WSL drive aliases while leaving /, /dev/null, and non-Windows platforms unchanged.
  • Add the coding-agent changelog entry for the Windows bash path fix.

Verification

bun test packages/coding-agent/test/tools/windows-drive-alias.test.ts packages/coding-agent/test/tools/root-path-alias.test.ts passed. cargo test --manifest-path crates/brush-core-vendored/Cargo.toml sys::fs::tests passed. cargo check -p pi-shell passed. bun check passed. I also attempted cargo test -p pi-shell; it currently fails in two embedded-session tests with exit 143 on this Linux runner after 797/799 tests pass. Fixes #2634

Mapped MSYS and WSL drive aliases before bash cwd validation and brush filesystem resolution so cd, stat-style tests, and tool cwd handling agree on Windows.

Fixes #2634

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 3f74f48776

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

}

function windowsDriveAliasPath(filePath: string): string | undefined {
const normalizedSeparators = filePath.replace(/\\/g, "/");

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve root-relative Windows paths

On win32 this rewrites all backslashes to / before detecting /d aliases, so a legitimate Windows rooted path such as \d\logs (which path.win32.resolve("C:\\work", "\\d\\logs") resolves on the current C: drive) is treated as the MSYS alias and becomes D:\logs. Any tool cwd/path argument for a root-level one-letter directory is then validated or read on the wrong drive; only forward-slash aliases should be translated.

Useful? React with 👍 / 👎.


#[cfg(any(windows, test))]
const fn is_path_separator(byte: u8) -> bool {
byte == b'/' || byte == b'\\'

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve root-relative paths in brush

On Windows this makes the alias detector accept a leading backslash as if it were an MSYS/WSL slash, so \d\logs is translated to D:\logs before absolute_path. A backslash-rooted path is a normal Windows path rooted on the current drive, so brush builtins like cd, redirects, and stat tests can operate on the wrong drive whenever the first root component is a single letter; restrict alias detection to / for the leading alias separators.

Useful? React with 👍 / 👎.

Restricted MSYS and WSL drive alias normalization to forward-slash roots so native Windows root-relative paths like \d\logs stay on the current drive.

Fixes #2634
@roboomp

roboomp commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator Author
  • Restricted normalizeWindowsDriveAliasPath to forward-slash aliases so native root-relative paths like \d\logs remain unchanged.
  • Restricted brush-core alias detection to forward-slash MSYS/WSL prefixes so \d\logs and \mnt\d\logs are not rewritten to another drive.
  • Added TypeScript and Rust regression assertions for those root-relative paths; focused tests and bun check passed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 7e6a5fa812

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

/// * `path` - The path to get the absolute form of.
pub fn absolute_path(&self, path: impl AsRef<Path>) -> PathBuf {
let path = path.as_ref();
let normalized_path = crate::sys::fs::normalize_shell_path(path.as_ref());

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Normalize aliases before pathname expansion

When the command word contains a glob, brush expands it in Pattern::expand before the command receives arguments; I checked that path in crates/brush-core-vendored/src/patterns.rs, and it builds roots via sys::fs::pattern_path_root and calls read_dir() directly without going through Shell::absolute_path. On Windows this means echo /d/project/*.txt still enumerates \d\project on the current drive (or leaves the literal unmatched) while /d/project/file.txt now works, because the new drive-alias translation is only installed on this absolute_path path. Please apply the same alias normalization in pathname expansion/root parsing.

Useful? React with 👍 / 👎.

Applied MSYS and WSL drive alias root parsing before brush pathname expansion reads directories, preserving backslash-rooted Windows paths.

Fixes #2634
@roboomp

roboomp commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator Author
  • Added brush pathname-expansion root parsing for forward-slash MSYS/WSL drive aliases so globs like /d/project/*.txt enumerate D:/project before read_dir().
  • Kept backslash-rooted Windows paths out of alias parsing, so \d\logs remains rooted on the current drive rather than becoming D:\logs.
  • Added root-parser unit coverage plus a Windows-only glob expansion regression; cargo test --manifest-path crates/brush-core-vendored/Cargo.toml sys::fs::tests, cargo test --manifest-path crates/brush-core-vendored/Cargo.toml patterns::tests, bun test packages/coding-agent/test/tools/windows-drive-alias.test.ts, and bun check passed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 6671e18994

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

native.push(':');
native.push('\\');
for &byte in tail {
native.push(if is_path_separator(byte) { '\\' } else { char::from(byte) });

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve Unicode when translating drive aliases

On Windows, when an MSYS/WSL alias contains non-ASCII path text such as /d/Users/José, tail is the UTF-8 byte sequence from path.to_str(), but this loop appends each byte as an independent Unicode scalar. That turns é into é, so brush builtins resolve the wrong native path for any aliased path with non-ASCII characters.

Useful? React with 👍 / 👎.

export function resolveToCwd(filePath: string, cwd: string): string {
const normalized = normalizeLocalScheme(filePath);
const expanded = expandPath(normalized);
const expanded = normalizeWindowsDriveAliasPath(expandPath(normalized));

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Preserve root-relative paths after search parsing

Fresh evidence: parseSearchPathPreferringLiteral still routes search/find targets through normalizePathSeparators before calling this resolver. On Windows, a root-relative backslash path like \d\logs becomes /d/logs, then this new alias normalization resolves it to D:\logs instead of path.win32.resolve(cwd, '\\d\\logs') on the current drive; the direct \d\logs test does not cover these parsed search/find paths.

Useful? React with 👍 / 👎.

@roboomp

roboomp commented Jun 15, 2026

Copy link
Copy Markdown
Collaborator Author
  • This is already addressed in pushed commit 7e6a5fa812a: windowsDriveAliasPath now returns before splitting unless the input starts with /, so \d\logs is preserved.
  • Regression coverage is present in packages/coding-agent/test/tools/windows-drive-alias.test.ts for \d\logs and \mnt\d\logs; bun test packages/coding-agent/test/tools/windows-drive-alias.test.ts passed.

@can1357 can1357 closed this in b371a16 Jun 15, 2026
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.

cd /d/... (Git-Bash/MSYS-style drive paths) fails on Windows while ls accepts them

1 participant