已合并
fix: 兼容Windows安装默认skills (#194) #1369
fix: 兼容Windows安装默认skills (#194) #1369
已合并
高煜博创建于 7月20日
高煜博
高煜博成员
7月20日

描述

.opencode/plugins/install-default-skills.ts 在 opencode 启动时自动安装四个默认 skill(gitcode-prgitcode-issueapi-doc-generatorgitcode-pipeline)。原实现委托给 bash 脚本 .claude/skills/default-skills/scripts/install-default-skills.sh,在 Windows 上存在两层阻断:

  1. TS 层短路:插件检测 bash --version,原生 Windows 无 bash 时直接 return,不执行任何安装。
  2. Shell 脚本依赖install-default-skills.sh 使用 ln -sfn(Windows 普通用户无符号链接权限)、/tmpmktemptimeout 等 Unix 专有依赖。

本 PR 用 Node.js 原生 API(fs/path/os/child_process)重写安装逻辑,移除 bash 硬依赖,使插件在 Linux 与 Windows 上均能自动安装默认 skills,且不破坏现有 Linux 行为。.sh 脚本保留不动,供 Claude Code 手动场景(Linux)使用。

变更类型

关联的Issue

#194

如何测试

前提条件:系统已安装 git 并在 PATH 中(Linux/Windows 均默认随 Git for Windows 提供)。

Linux 验证(等价性)

  1. 删除 .claude/skills/_remote/ 与四个 skill 一级目录链接
  2. 启动 opencode,观察安装提示
  3. 验证 _remote/<skill>/ 真实目录存在,一级目录为相对路径符号链接 -> _remote/<skill>
  4. 二次启动,hash 无变化时不打印重启提示(幂等)
  5. 断网启动,验证 .opencode_skills_error marker 与提示打印

Windows 验证(新增能力)

  1. 原生 Windows(无 bash)启动 opencode
  2. 验证四个 skill 自动安装,一级目录为 junction(无需管理员权限)
  3. 验证 _remote/<skill>/SKILL.md 可访问

已执行验证(Linux 环境):

  • TypeScript strict 模式类型检查通过
  • fresh install:4 个 skill 以相对路径符号链接安装,SKILL.md 可访问
  • 幂等性:二次运行无变更提示、无错误 marker
  • 错误路径:模拟 git clone 失败,正确写 .opencode_skills_error 并打印清晰提示
  • .gitignore 幂等更新,无重复条目,保留尾换行

核对清单

其他信息

设计要点

  • 跨平台链接:Windows 用 junction(无需管理员权限,目录专用,target 用绝对路径);Unix 保持相对路径 symlink(与 .sh ln -sfn _remote/$skill 等价,仓库内可移植)
  • 临时目录:os.tmpdir() + fs.mkdtempSync 替代硬编码 /tmptry/finally + fs.rmSync force:true 清理
  • 网络探测合并到 git clone --depth 1(带 CLONE_TIMEOUT_MS 超时),去除 curl/wget 预检测,减少平台分支
  • DEFAULT_SKILLSREPO_URLCLONE_TIMEOUT_MS 集中在文件顶部常量,与 .sh 保持一致
  • 错误处理保留原有 .opencode_skills_error marker + 延迟打印逻辑,catch 块按 strict 模式类型安全处理 unknown error

红线自查:不硬编码敏感信息(REPO_URL 为公开仓库地址);资源释放覆盖异常分支;仅改 TS 插件,不扩大修改范围。

安全提示:issue 创建过程中误建了测试 issue #193(已加评论标注,GitCode API 关闭端点有兼容问题未能关闭,请社区协助关闭)。

likedislike
Pull Request已成功合入, 合并人@CANN-robot
(感谢 高煜博 的贡献)
高煜博高煜博成员
7月20日 创建了 pull request,commit 9e3f28fb
高煜博高煜博成员
7月20日 关联了issue:test issue please ignore,[Bug-Report|缺陷反馈]: opencode插件 install-default-skills.ts 在 Windows 上无法自动安装默认skills
atomgit-bot
atomgit-bot
7月20日 评论:

变更摘要

此 PR 重写了 .opencode/plugins/install-default-skills.ts 中默认 skills 的安装逻辑,用 Node.js 原生 API(fs/path/os/child_process)替代原有的 bash 脚本调用,移除了对 Unix 环境的硬依赖,使插件在 Linux 与 Windows 上均能自动完成四个默认 skill(gitcode-prgitcode-issueapi-doc-generatorgitcode-pipeline)的安装。

主要改动

  • 移除 bash 环境检测与脚本调用:删除了 hasBash 检测逻辑(原通过 bash --version 判断)及 bash ${scriptPath} 调用,消除 Windows 上因缺少 bash 导致的安装短路。
  • 新增 cloneSkillsRepo 函数:使用 execFileSync 调用 git clone --depth 1REPO_URL 浅克隆 skills 仓库,并设 20 秒超时,替代原 shell 脚本中的网络拉取逻辑。
  • 新增 createSkillLink 跨平台符号链接函数:在 Linux 上使用 path.relative 计算相对路径后调用 fs.symlinkSync,在 Windows(win32)上使用 junction 选项创建目录链接,解决原 ln -sfn 在 Windows 下的权限问题。
  • 新增 installSkillsToRemote 核心安装函数:在临时目录中克隆仓库 → 将各 skill 目录复制到 _remote/ → 创建一级目录链接 → 调用 ensureGitignore 维护 .gitignore 条目,完成后清理临时目录。
  • 新增 ensureGitignore 函数:自动读取根目录 .gitignore,对每个默认 skill 检查是否存在 .claude/skills/<skill> 条目,缺失则追加。
  • 错误处理兼容 Windows:将捕获的错误断言为 Error & { stderr?: Buffer },对 stderrtoString() 转换后再写入日志,兼容 Windows 上 execFileSync 返回 Buffer 类型的情况。
likedislike
atomgit-bot
atomgit-bot
7月20日 评论:

代码审查

审查总结

我对 .opencode/plugins/install-default-skills.ts 的变更进行了全面审查,共发现 3 个问题

优先级 数量 说明
P1 1 Windows junction 创建时 targetPath 可能为相对路径,导致 fs.symlinkSync 抛出异常,Windows 上默认 skills 安装失败——正是本 PR 要修复的核心场景
P2 1 execFileSync 超时错误的检测逻辑(error.message.includes("timed out"))在 Node.js 18+ 中无法匹配,超时场景将退化到通用错误提示
P3 1 catch 块未记录 error.stdout,对于 git clone 等操作可能导致错误日志不完整(可选改进)

整体风险判断:中等风险。P1 问题会直接导致 Windows 上默认 skills 安装失败(PR 的核心目标受损),需要在合入前修复;P2 问题影响超时场景下的用户体验;P3 为可选改进项。

已审查文件

  • .opencode/plugins/install-default-skills.ts ✅ — 发现 3 个问题
类型 数量
🔴 阻塞 1
🟡 建议 1

⛔ 需要修改

likedislike
CANN-robotCANN-robot成员
7月20日 添加了label:cann-cla/yes
CANN-robot
CANN-robot成员
7月20日 评论:

Thanks for your pull-request.
The full list of commands accepted by me can be found at here
You can get sig-info at here


PR Approval Progress

Congratulations! All modules have met the lgtm and approve requirements.

Module Approval Details

module lgtm status approve status
repo-cann/graph-autofusion zhanj, xchu42, xuyafei (3/2) zhanj (1/1)

💡 Tip:

  • Committer can comment /approve or /lgtm
  • Commenting /approve implies both code review (lgtm) and intent to merge (approve)

CLA Signature Pass

hamburgerbobo, thanks for your pull request. All authors of the commits have signed the CLA. 👍

likedislike
CANN-robotCANN-robot成员
7月20日 将yangyongqiang0606,xchu42,zhang_shengjie,depeng1994,wqtshg_wt,peiyang,shengnan666,XuebinYang,sjtulxh,zhanj,zhujingjing,xuyafei,wangxiaotian995设为评审人
此处折叠了11条事件消息 查看更多
CANN-robotCANN-robot成员
7月21日 删除了label:stat/needs-squash
CANN-robot
CANN-robot成员
7月21日 评论:

Notification

This pull request has been changed(code update) or closed, so removes the following label(s): stat/needs-squash.

likedislike
高煜博
高煜博成员
7月21日 评论:

/compile

likedislike
高煜博高煜博成员
7月21日 update merge request[project id: 8005834, iid: 1369, commit_id: 0b68368141be1996d8c0fc026f1dd8993ae3c05f] virtual merging success
CANN-robotCANN-robot成员
7月21日 添加了label:ci-pipeline-running
CANN-robot
CANN-robot成员
7月21日 评论:

流水线任务触发成功
任务链接 [92ecc5d8e9fb42e3a78738a96f68357b][流水线指导]

任务名称状态日志下载链接
codecheck ✅ SUCCESS >>>>>
SCA ✅ SUCCESS >>>>>
antipoison ✅ SUCCESS >>>>>
codecheck_checkpr ✅ SUCCESS
pre_comment ✅ SUCCESS >>>>>
codecheck_codestyle ✅ SUCCESS >>>>>
codecheck_precommit ✅ SUCCESS >>>>>

[2026-07-21 10:19:37]    CI执行结束

likedislike
CANN-robot
CANN-robot成员
7月21日 评论:

流水线任务触发成功
任务链接 [bdac8945096c4445a8164a7ddca10f32][流水线指导]

任务名称状态日志下载链接
UT_Test_Python_superkernel ✅ SUCCESS >>>>>
ST_Test_Python_superkernel ✅ SUCCESS >>>>>
UT_Test_superkernel ✅ SUCCESS >>>>>
UT_Test_autofuse_framework ✅ SUCCESS >>>>>
ST_Test_autofuse_framework ✅ SUCCESS >>>>>
UT_Test_autofuse_ascendc_api ✅ SUCCESS >>>>>
ST_Test_autofuse_ascendc_api ✅ SUCCESS >>>>>
ST_Test_autofuse_e2e ✅ SUCCESS >>>>>
pre_comment ✅ SUCCESS >>>>>

[2026-07-21 10:19:04]    CI执行结束

likedislike
CANN-robot
CANN-robot成员
7月21日 评论:

流水线任务触发成功
任务链接 [72c70c109ceb4ac4ad2cab477442b78f][流水线指导]

任务名称状态日志下载链接
Compile_Ascend_X86 ✅ SUCCESS >>>>> >>>>>
Compile_Ascend_ARM ✅ SUCCESS >>>>> >>>>>
pre_comment ✅ SUCCESS >>>>>
PreSmoke_A900_npupool ✅ SUCCESS >>>>>

[2026-07-21 10:26:02]    CI执行结束

likedislike
CANN-robotCANN-robot成员
7月21日 删除了label:ci-pipeline-running
CANN-robotCANN-robot成员
7月21日 添加了label:ci-pipeline-passed
xchu42
xchu42成员
7月21日 评论:

/lgtm

likedislike
xuyafei成员
7月21日 评论:

/lgtm

likedislike
CANN-robotCANN-robot成员
7月21日 添加了label:lgtm
atomgit-bot
atomgit-bot7月21日进行代码检视1
.opencode/plugins/install-default-skills.ts
@@ -106,3 +171,4 @@
171+ if (stderrStr) log(`stderr from error: ${stderrStr}`)
106172 const errorMarkerPath = path.join(rootDir, ".opencode_skills_error")
107173 let detail = ""
108174 if (error.message && error.message.includes("timed out")) {
atomgit-bot
atomgit-bot7月21日评论:

🟡 Medium Priority

变更链路:原代码通过 $bash ...`` 调用 shell 脚本,超时由 timeout 命令产生,错误信息中包含 "timed out" 字符串。PR 将调用替换为 execFileSync('git', ['clone', ...], { timeout: CLONE_TIMEOUT_MS })(第 80 行),但在第 174 行的 catch 块中仍使用 error.message.includes("timed out") 检测超时。

在 Node.js 18+ 中,execFileSync 超时行为为:向子进程发送 SIGTERM → 子进程被杀死 → 抛出 Error 对象,其 error.killed === trueerror.signal === 'SIGTERM',但 error.message 中不包含 "timed out" 字符串(格式类似 "Command failed: git clone ... \n")。

因此第 174 行的 error.message.includes("timed out") 将始终返回 false,导致超时场景落入第 176–177 行的通用错误分支,用户无法看到第 175 行的超时专用中文提示("网络连接超时,无法访问远程仓库。请检查网络连接后重试。"),体验退化。

触发条件:git clone 操作超时(网络慢或仓库不可达,超过 20 秒)。

建议:将超时检测从单一的 error.message.includes("timed out") 扩展为同时检查 error.killed 属性。execFileSync 超时时会杀死子进程,设置 error.killed = trueerror.signal = 'SIGTERM',这比检查 message 字符串更可靠。也可以保留原字符串检测作为向后兼容。

改动建议
174
- if (error.message && error.message.includes("timed out")) {
174
+ if (error.message && (error.message.includes("timed out") || (error as any).killed)) {
应用建议
likedislike
atomgit-bot
atomgit-bot7月21日进行代码检视1
.opencode/plugins/install-default-skills.ts
@@ -77,0 +46,4 @@
46+function createSkillLink(targetPath: string, linkPath: string): void {
47+ fs.rmSync(linkPath, { recursive: true, force: true })
48+ if (process.platform === 'win32') {
49+ fs.symlinkSync(targetPath, linkPath, 'junction')
atomgit-bot
atomgit-bot7月21日评论:

🟠 High Priority

变更链路:InstallSkillsPlugin 接收的 directory 参数未经过 path.resolve() 处理 → findGitRoot(directory) 在第 35–44 行直接返回 startDir(可能是相对路径如 ".") → installSkillsToRemote(rootDir) 在第 87–88 行通过 path.join 拼接出 skillsDir/remoteDir → 第 107 行的 dest(即 _remote/<skill> 真实目录路径)同样继承了相对性 → 第 110 行调用 createSkillLink(dest, ...)

createSkillLink 的第 49 行,Windows 分支执行 fs.symlinkSync(targetPath, linkPath, 'junction')。Node.js 文档明确要求:Windows junction 的 target 必须是绝对路径("Junctions require the destination path to be absolute")。当 targetPath 为相对路径(如 .claude/skills/_remote/gitcode-pr)时,fs.symlinkSync 会抛出异常,导致整个安装流程失败。

这正是本 PR 要解决的 Windows 兼容性问题的核心场景——PR 设计文档也明确写了"Windows junction 不需要管理员权限,但仅对目录有效且 target 需绝对路径"。当前实现违背了这一设计约束。

触发条件:opencode 传入的 directory 为非绝对路径(例如工作目录为 "." 、相对路径 "../project" 等),这在实践中很常见。

建议:在 createSkillLink 的 Windows 分支中,对 targetPath 使用 path.resolve() 转为绝对路径后再传给 fs.symlinkSync。同时也可以考虑在 installSkillsToRemote 入口处对 rootDir 做一次 path.resolve(),确保所有衍生路径都是绝对的。

改动建议
49
- fs.symlinkSync(targetPath, linkPath, 'junction')
49
+ fs.symlinkSync(path.resolve(targetPath), linkPath, 'junction')
应用建议
likedislike
zhanj
zhanj成员
7月21日 评论:

/approve

likedislike
CANN-robotCANN-robot成员
7月21日 添加了label:approved
CANN-robotCANN-robot成员
7月21日 解决了最后一个问题
CANN-robotCANN-robot成员
7月21日 合入了pull request