Skip to content

[Bug] Windows 下 skill setup 创建的 Agent 适配入口(符号链接)无法被 Node 读取,技能实际不可用 #1421

Closed

Description

@JohnMax-clearlove

Describe the Bug

dws skill setup --mode mono --target all --yes 报告 installed=7 且逐项 ✓,但它为各 Agent 目录创建的 skills/dws 是符号链接。用 Node 的 fs.statSync 访问时报 EPERM,Agent 的技能加载器只能静默跳过,最终表现为“setup 显示成功,但 Agent 用不到技能”。

只有直接读共享目录 ~/.agents/skills 的 Agent 不受影响;不读共享目录的 Agent 完全拿不到技能。

Steps to Reproduce

  1. Windows 上执行:

    dws skill setup --mode mono --target all --yes
  2. 用 Node 读取任一新创建的适配入口:

    node -e "require('fs').statSync(process.env.USERPROFILE + '/.pi/agent/skills/dws')"
  3. 报错:

    EPERM: operation not permitted, stat 'C:\Users\<用户>\.pi\agent\skills\dws'
    

补充:fs.lstatSync 能成功且 isSymbolicLink() === true,只有跟随符号链接的 fs.statSync 失败。

对照实验(同一台机器、同一路径层级,临时目录用完即删)——目录联接可被正常跟随:

cmd /c mklink /J "$env:TEMP\dws-junc" "$env:USERPROFILE\.agents\skills\dws"
node -e "require('fs').statSync(process.env.TEMP + '/dws-junc')"   # 正常返回
cmd /c rmdir "$env:TEMP\dws-junc"

Expected Behavior

  • setup 为 Agent 创建的入口应可被该 Agent 读取(目录联接,或该 Agent 真正支持的注册方式);
  • 至少应在创建后做一次可读性校验,校验失败时不要输出 ✓。

Actual Behavior

6 个适配入口全部不可读(EPERM):.pi\agent\skills、.qoder\skills、.qoder-cn\skills、.qoderwork\skills、.codebuddy\skills、.zcode\skills;setup 仍逐项输出 ✓,用户无法察觉。

已验证的临时处理:把符号链接换成目录联接后,同一路径 fs.statSync 正常、SKILL.md 可读,技能被正确加载(实测:技能名 dws 恰好 1 条、0 碰撞诊断、cli_version: ">=1.0.61" 与二进制一致)。

Environment

  • OS: Windows 11(10.0.22631,家庭中文版)
  • Architecture: amd64
  • CLI Version: v1.0.62(build 2026-09-16T08:35:04Z,commit 70323e1),npm install -g dingtalk-workspace-cli
  • Go Version: 未从源码构建(n/a)
  • 复现用 Node: v25.9.0

Additional Context

  • 技能版本与二进制同步(cli_version: ">=1.0.61"),不是技能内容问题;
  • 影响面取决于各 Agent 是否直接读 ~/.agents/skills:读的(如 pi)侥幸可用,不读的完全不可用;
  • 相关命令:dws skill setup --mode mono --target all。

Activity

  1. Frank-Chan commented on Oct 10, 2026

    @Frank-Chan

    补充一个更严重的下游影响,v1.0.63(build 2026-10-03) 仍然存在。

    这个形态不只让 Node 读不到,还会静默破坏基于 robocopy 的备份,而且完全不报错。

    环境:Windows 11,dws v1.0.63,dws skill setup 生成的 16 个 dingtalk-* 适配入口。

    1. robocopy 无法读取该入口

    robocopy <src> <dst> /MIR /R:0 /W:0
    → 错误 5 (0x00000005) 正在复制文件 ...\dingtalk-doc   原因: 拒绝访问。
    → 退出码 9
    

    同场对照:把入口换成目录联接(mklink /J)后,同一目录退出码为 1(正常),内容完整。

    2. 更严重:/MIR 把它当空目录同步,目标端被写成 0 字节文件

    我用 robocopy /MIR 把各 Agent 的 skills 目录镜像到备份仓库,结果仓库里这 16 个技能目录全部变成 0 字节文件——源端条目读不出来,/MIR 就按「空」处理。技能内容静默丢失,过程没有任何报错,备份静默失效近一个月才被发现。

    也就是说,这个缺陷除了「技能用不上」,还会破坏别人已有的备份数据。

    3. 影响范围

    .claude/skills、~/.openclaw/skills、hermes skills 各 16 个入口,共 48 个。同一批目录里其他来源安装的 lark-* 是正常的目录联接,所以问题只出在 dws 创建的这批。

    4. 判定与临时处理(供其他人参考)

    判定:

    • Get-Item <入口> -Force | Select LinkType → SymbolicLink(异常) vs Junction(正常)
    • cmd /c dir /AL → <SYMLINK> vs <JUNCTION>
    • 注意:Test-Path <入口> -PathType Container 对异常形态返回 False,不能拿它判目录

    原地转目录联接(目标不变、无需管理员权限):

    $li  = Get-Item -LiteralPath $link -Force
    $raw = "$($li.Target)"
    $abs = if ([IO.Path]::IsPathRooted($raw)) { $raw }
           else { [IO.Path]::GetFullPath((Join-Path $li.DirectoryName $raw)) }
    [IO.File]::Delete($link)                                  # 不能用 rmdir,对它恒返回 267(目录名无效)
    New-Item -ItemType Junction -Path $link -Target $abs

    两个坑:

    1. rmdir 删不掉这个形态,必须按文件删;
    2. Target 是相对路径(如 ..\..\.agents\skills\dingtalk-doc),必须先 GetFullPath 转绝对路径,否则建不了联接。

    期望

    • setup 创建入口时直接用目录联接(Windows 上 os.Symlink 不带目录标志就会得到这个形态);
    • 至少在创建后做一次可读性校验,校验失败不要输出 ✓。
  2. haofeng0705 commented on Oct 20, 2026

    @haofeng0705
    Collaborator

    Hi @JohnMax-clearlove,感谢你提供详细的排查和目录联接对照实验,也感谢 @Frank-Chan 补充 robocopy 备份受影响的情况。理解这种“安装成功但技能不可用、甚至影响备份”的体验确实很困扰。

    这是 DWS CLI 技能安装链路的缺陷,修复已经合入 #1529(提交 174e2147)。截至本次回复,公开发布的 v1.0.64 和 v1.0.65-beta.2 尚未包含该修复。

    根因

    dws skill setup 会先在临时子目录里创建链接,再移动到最终位置;但链接中的相对路径按最终位置计算,在临时位置上解析不到目标。

    Windows 上,Go 的 os.Symlink 会在创建时检查目标是否为目录。目标解析不到时会创建文件型符号链接,所以 dir /AL 显示的是 <SYMLINK>,不是 <SYMLINKD>。发布后它实际指向目录,Node、robocopy 等工具跟随访问时就会失败。旧版也缺少发布前可读性检查,因此仍然输出 ✓。

    以上是静态分析判断;我们在 macOS 隔离环境观察到了临时链接的相对目标无法解析,但没有在 Windows 原生环境实际复现本 Issue 的 EPERM 或完整验证 Node/robocopy 场景。

    修复方式

    1. Windows 上改用指向绝对目标的目录联接(junction)。
    2. skill setup 发布前检查目录和 SKILL.md 可读性,检查失败时尝试复制安装,而不是直接报告链接安装成功。
    3. 用包含修复的版本重新执行 dws skill setup,会替换旧版留下的符号链接。

    如何处理已有入口

    1. 新版本发布前,可参考 @Frank-Chan 上方提供的方案,将异常入口改成目录联接。请先备份并确认入口路径;转换后暂不要再次运行旧版 dws skill setup,根据代码分析,它可能把联接重新换回异常符号链接。

    2. 包含修复的版本发布后,通过原安装方式升级,并显式执行一次 setup,不能只依赖升级时的自动技能同步:

      dws skill setup --mode mono --target all --yes

      multi 模式请改用 --mode multi。

    3. 重启 Agent,并检查入口类型和可读性(multi 模式请改成对应的 dingtalk-* 入口):

      Get-Item "$env:USERPROFILE\.pi\agent\skills\dws" -Force | Select-Object LinkType, Target
      node -e "const fs=require('fs'); const p=process.env.USERPROFILE+'/.pi/agent/skills/dws'; fs.statSync(p); fs.readFileSync(p+'/SKILL.md')"

      预期 LinkType 为 Junction,Node 不再报错;若联接不可用而回退为普通目录复制,则应同样可读。

    4. 若使用过 robocopy /MIR 备份,请先核对备份中的技能内容,再重新备份;替换入口不会恢复已经丢失的备份内容。

    我们先按修复已合入关闭此 Issue,不代表修复已发布或你的 Windows 场景已完成验证。包含修复的版本发布后,如果仍有问题,欢迎继续在这里留言,并提供 dws version、Windows/Node 版本、完整命令及上述检查结果(请脱敏),我们会继续跟进。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions