DeepSeek Harness 的 WSL UNC 工作区能读不能写?EIO Win32 1 排查
DeepSeek Harness 能读、能列目录、能搜索,唯独写不了——这不是权限问题,而是工作区准入与文件后端对「支持的文件系统」定义不一致。 准入只看路径拼写,UNC 通过了;写入却假设 NTFS 语义,GetFileSecurityW 在 9P 共享上返回 ERROR_INVALID_FUNCTION,报出来就是 EIO (Win32 1)。据讨论,报告者在桌面端复现,社区成员核对了 workspace 与 fs-local 的源码(讨论原文)。
DeepSeek Harness 的 WSL UNC 工作区:读、写、终端各有各的报错
同一个工作区在四类操作上表现不同,先按操作分类记录,才能判断是准入问题还是后端能力问题。 复现顺序:
- 在桌面端打开
\\wsl.localhost\Ubuntu\home\you\proj作为工作区,预期能正常加载。 - 让模型编辑一个已存在的文件,预期报
Error: GetFileSecurityW EIO (Win32 1): \\wsl.localhost\...。 - 让模型新建一个文件,预期报
FS_IO_ERROR,消息里带cannot write,错误码不是ENOTSUP。 - 打开工作区里的终端标签,预期落在
cmd.exe并提示「UNC 路径不受支持」,当前目录被放弃。 - 读取普通文件、列目录,预期都成功;但用
Measure-Command统计一次stat,预期单文件约 17000 微秒,而 WSL 内部约 86 微秒。 - 访问
/etc/os-release、/lib这类符号链接,预期ENOENT——共享把 Linux 符号链接暴露成 Windows 无法解析的重解析点。
读成功不代表工作区可用,它只说明 9P 的读路径够用。
准入为什么放行 UNC:它只排除了两种根写法
校验的本意是拒绝「相对盘符」和「根相对」两种写法,UNC 只是不在它的拒绝清单里,并不是被判定为受支持。 社区核对到的谓词是:
if (platform !== 'win32') return posix.isAbsolute(path)
const root = win32.parse(path).root
return win32.isAbsolute(path) && root !== '\\' && root !== '/'
对 \\wsl.localhost\Ubuntu\home\you\proj,win32.parse(path).root 是 \\wsl.localhost\Ubuntu\,两个被拒写法都不匹配,于是守卫放行。之后只跑一次 realpathNormalize,没有任何文件系统能力检查。准入与后端之间没有共享的「该卷支持什么」信息,失败只能等到第一次操作才暴露。
写入为什么必然失败:GetFileSecurityW 在 9P 上没有对应实现
Windows 发布文件时先复制源文件 DACL,再替换目标,这两步都假设安全描述符可用;9P 两样都不提供。 同类 DACL 失败还会出现在 Windows 沙箱命令里,见 Windows ACL 沙箱命令失败排查。链条是:
fs-local/src/win32.ts的:92-97调用GetFileSecurityW(nativePath, DACL_SECURITY_INFORMATION, ...)。- 返回值为 0 时抛
win32Error('GetFileSecurityW', api.getLastError(), path),而 9P 给的是错误码 1。 - 同一文件的
:122-132走ReplaceFileW发布,这也不是 9P 的原语。 - 新建文件走
link(temp, dest)的保守护卫,9P 拒绝硬链接返回ENOTSUP,但被 fs 层包装成通用的FS_IO_ERROR,errno 只留在消息文本里。 - 结论:DACL 复制发布在这类共享上没有意义,不是映射表少了一行。
路径一:在 DeepSeek Harness 准入处直接拒绝 UNC
最诚实的修法是在采用阶段就说不支持,让用户在看到 EIO 之前就知道这条路走不通。 步骤:
- 在
paths.ts的 win32 分支里补一条根判断:root.startsWith('\\\\')即视为 UNC。 - 命中时返回明确的「不支持的工作区类型」错误,而不是
false后静默落到别的分支。 - 覆盖
\\wsl.localhost\、\\wsl$\、\\?\UNC\、\\server\share\四种拼写,各跑一次添加工作区,预期都被拒绝。 - 在 NTFS 盘符路径上回归一次,预期添加与读写都不变。
代价要接受:现在能读能列的那批 UNC 工作区会被一并拒掉,包括有意的网络共享。
路径二:保留 DeepSeek Harness 准入,首次写报能力错误
如果不想砍掉现有的只读用法,可以继续放行,但把失败提前到第一次写,并把错误说成「这个文件系统不支持」而不是「某个 API 失败了」。 步骤:
- 在写入入口先探测目标卷的能力,或在
GetFileSecurityW失败且错误码为 1 时识别为「不支持」。 - 抛出带文件系统类型的错误,例如「UNC/9P 工作区不支持 ACL 复制发布,请改用盘符或 WSL 侧运行」。
- 给这类工作区加一条常驻提示,说明只能读、不能写。
- 复测:读、列目录、搜索全部保持可用;写操作立即失败且错误文本可读。
- 顺手把每次
stat的 17000 微秒成本写进提示,避免用户在大目录上白等。 - 把「只读工作区」的标识存进工作区记录,重启后提示仍然存在,不依赖当次会话内存。
路径三:改用盘符映射,并复测 DeepSeek Harness 的 DACL 复制
映射盘符能解决「准入不认」和「cmd.exe 不认 UNC」两个问题,但它不改变底层还是 9P,写入是否可用必须实测。 步骤:
- 确认 WSL 发行版正在运行:
wsl -l -v,预期状态为Running。 - 执行
net use W: \\wsl.localhost\Ubuntu\home\you\proj /persistent:yes,预期提示命令成功完成。 - 把
W:\添加为工作区,预期通过准入,因为根已经变成盘符根。 - 编辑一个已存在文件,预期两种结果之一:成功,说明该映射下 DACL 调用可用;仍报
EIO (Win32 1),说明底层语义没变,回到路径一或路径二。 - 新建一个文件,预期不再出现
FS_IO_ERROR;若报ENOTSUP,同样说明硬链接仍不可用。 - 在
W:\上开终端,预期不再提示 UNC 不支持。 - 用
net use W: /delete断开后重新打开工作区,预期出现明确错误而不是目录悄悄失效。
盘符映射不是万能钥匙:它绕过的是路径拼写检查,不是文件系统能力。
DeepSeek Harness 怎么选:按你是否需要写
三条路径的分界线只有一条:这个工作区要不要写。 对照选择:
| 你的需求 | 选哪条 | 结果 |
|---|---|---|
| 只读浏览 WSL 项目 | 路径二 | 保留现状,错误可读 |
| 必须写,且能改宿主 | 路径一加 WSL 侧运行 | 采用即拒绝,不再误导 |
| 必须写,且要用 Windows 侧工具 | 路径三 | 先映射盘符,再实测写入 |
| 团队统一环境 | 路径一 | 从一开始就不给错误预期 |
| 只想临时改一个文件 | 路径三 | 映射后先跑一次写测试 |
最后提醒一句:dsh-plugin-wsl-env 这类 DSH插件 走的是常驻代理路线,能接管文件与 shell,但它不是宿主内置能力,升级与兼容要自己盯(据讨论)。
不管选哪条,先把「这个工作区支持写吗」作为采用前的问题问一次,比在第一次编辑时看到 EIO 再回头查便宜得多。
常见问题
DeepSeek Harness 的写入走的是 Windows 的 DACL 复制路径,GetFileSecurityW 在 9P 共享上返回 ERROR_INVALID_FUNCTION(Win32 1),被上层包装成 EIO。读取不经过这条路径,所以读和列目录看起来完全正常。
DeepSeek Harness 的工作区准入只检查路径是否绝对、根是否等于反斜杠或斜杠。UNC 的根是 \\wsl.localhost\Ubuntu\,既不是 \ 也不是 /,于是通过校验;它通过的含义是「不属于被拒的两种写法」,不是「这个文件系统被支持」。
DeepSeek Harness 下把 WSL 目录映射成盘符,只能绕过准入和终端对 UNC 的限制,不一定绕过后端。盘符仍然指向同一个 9P 共享,GetFileSecurityW 与 ReplaceFileW 是否可用取决于底层实现,映射后必须重新测一次写入。
有:DeepSeek Harness 可以在 WSL 侧运行,让工作区是 Linux 本地路径;或者使用讨论中提到的第三方插件 dsh-plugin-wsl-env,由常驻代理接管文件与 shell。两条路都避开 Windows 的 UNC 与 NTFS 假设。
相关术语
- UNC path
- UNC 路径是以两个反斜杠开头的网络资源路径,形如 `\\server\share\dir`,没有盘符。它由网络重定向器解析,能力取决于服务端实现了哪些文件系统操作。— https://learn.microsoft.com/en-us/windows/win32/fileio/naming-a-file
- 9P
- 9P 是 WSL 用来把 Linux 文件系统暴露给 Windows 的共享协议,`\\wsl.localhost` 与 `\\wsl$` 都通过它访问。它只提供基础文件操作,不实现 Windows 安全描述符相关调用。— https://github.com/deepseek-ai/deepseek-harness/discussions/10260
- ERROR_INVALID_FUNCTION
- ERROR_INVALID_FUNCTION 是 Win32 错误码 1,表示目标对象不支持所请求的功能。它在 9P 共享上由 `GetFileSecurityW` 返回,被上层显示为 `EIO (Win32 1)`。— https://learn.microsoft.com/en-us/windows/win32/debug/system-error-codes--0-499-
- GetFileSecurityW
- GetFileSecurityW 是读取对象安全描述符的 Win32 API,fs-local 在 Windows 发布文件时用它复制源文件 DACL。依赖安全描述符存储的文件系统才能正常返回。— https://learn.microsoft.com/en-us/windows/win32/api/securitybaseapi/nf-securitybaseapi-getfilesecurityw
来源
- deepseek-harness Discussion #10260:工作区校验放行 WSL UNC 路径,但文件工具链无法写入· deepseek-ai(GitHub Discussions)
- deepseek-harness 仓库:核对 workspace/src/paths.ts 与 fs-local/src/win32.ts· deepseek-ai(GitHub)