Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 8 additions & 2 deletions crates/host-core/src/tools/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ use tokio::io::{AsyncRead, AsyncReadExt};
use tokio::process::{ChildStderr, ChildStdin, ChildStdout, Command};
use tokio::sync::{mpsc, watch};

use crate::workspace::{resolve_tool_path_with_external, ToolRoot};
use crate::workspace::{resolve_tool_path_with_external, simple_canonicalize, ToolRoot};

mod grep_rg;
pub mod hashline;
Expand Down Expand Up @@ -2625,7 +2625,13 @@ fn relative_display(root: &Path, path: &Path) -> String {
// `path` comes back canonicalized from the resolver; strip against the
// canonical root spelling too, or symlinked roots (macOS /var vs
// /private/var) would render absolute.
let canonical_root = root.canonicalize().unwrap_or_else(|_| root.to_path_buf());
//
// The resolver spells paths with `simple_canonicalize`, so the root must
// use that same spelling: std `Path::canonicalize` keeps the Windows
// `\\?\` prefix, which never matches a resolved path and made every
// workspace-relative label fall back to an absolute one.
let canonical_root =
simple_canonicalize(root).unwrap_or_else(|_| root.to_path_buf());
path.strip_prefix(&canonical_root)
.or_else(|_| path.strip_prefix(root))
.unwrap_or(path)
Expand Down
7 changes: 6 additions & 1 deletion crates/host-core/src/workspace.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ use std::path::{Component, Path, PathBuf};
/// Canonicalize a path, stripping the Windows extended-length prefix (`\\?\`)
/// when the result is a simple drive-letter path (e.g. `C:\...`). This keeps
/// paths compatible with shell APIs (`ShellExecuteW`) that reject `\\?\`.
fn simple_canonicalize(path: &Path) -> std::io::Result<PathBuf> {
///
/// Every path the resolver returns carries this spelling, so any caller that
/// compares a resolved path against a root must canonicalize that root the
/// same way — std `Path::canonicalize` keeps the `\\?\` prefix on Windows and
/// would never match.
pub(crate) fn simple_canonicalize(path: &Path) -> std::io::Result<PathBuf> {
let canonical = path.canonicalize()?;
#[cfg(windows)]
{
Expand Down
6 changes: 5 additions & 1 deletion docs/spec/06-delivery/04-e2e-test-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -1394,7 +1394,11 @@ Each scenario is documented in this format:
- **Acceptance**: E (bounded cross-platform search)
- **Milestone**: M5
- **Status**: Unit-covered (host-core and agent-runtime); live multi-platform
protocol capture pending
protocol capture pending. The workspace-relative path expectation is covered
on Windows by `relative_display`, which must canonicalize the workspace root
with the resolver's own spelling (`simple_canonicalize`) — std
`Path::canonicalize` keeps the `\\?\` prefix there and silently degrades
every label to an absolute path.

#### E2E-019a: Scratch-directory writes stay out of the workspace (D114)

Expand Down
6 changes: 5 additions & 1 deletion docs/zh-CN/spec/06-delivery/04-e2e-test-plan.md
Original file line number Diff line number Diff line change
Expand Up @@ -800,7 +800,11 @@ M5。
- **接受**:E(有界跨平台搜索)
- **里程碑**:M5
- **状态**:单元覆盖(host-core 和代理运行时);多平台直播
协议捕获待处理
协议捕获待处理。工作区相对路径这一预期在 Windows 上由
`relative_display` 覆盖:它必须用解析器自身的拼写
(`simple_canonicalize`)规范化工作区根目录——标准库
`Path::canonicalize` 在 Windows 上会保留 `\\?\` 前缀,从而把所有标签
静默降级为绝对路径。

#### E2E-019a:暂存目录写入不在工作区中 (D114)

Expand Down