返回博客列表

OpenClaw PR #86261:Plugin Skills 在 Sandbox 模式下的同步修复

Plugin skills 在 sandbox 模式下无法被 agent 读取,原因是 symlink 的 syncSourceDir 未设置。修复后获得 diamond lobster 评级。

#OpenClaw#Open Source#PR#Sandbox#Symlink

Issue #86190

问题:Plugin skills 在 sandbox 模式下无法被 agent 读取。

错误信息

Path escapes sandbox root: ~/.openclaw/plugin-skills/xxx/SKILL.md

原因分析

Plugin Skills 的存储方式

Plugin skills 存储在 ~/.openclaw/plugin-skills/ 目录下,以 symlink 形式存在:

~/.openclaw/plugin-skills/browser-automation  →  /path/to/plugin/skills/browser-automation

这是 publishPluginSkills 函数创建的,用于暴露 plugin 提供的 skills。

Sandbox 的文件访问机制

Sandbox 模式下,agent 运行在 Docker 容器中。容器的 read tool 只能访问 /workspace 目录。

为了让 agent 能读取 skills,syncSkillsToWorkspace 函数负责将 skills 复制到容器的 /workspace/skills/ 目录。

同步失败的原因

const sourceDir = entry.syncSourceDir ?? entry.skill.baseDir;

对于 plugin skills:

  • entry.skill.baseDir = symlink 路径
  • entry.syncSourceDir = undefined(未设置)

结果:尝试从 symlink 路径复制,但 symlink 路径是 host 路径,容器无法访问。

为什么其他 skills 不受影响

Core skills 直接存储在 ~/.openclaw/skills/ 目录下,是真实的目录路径,syncSourceDir 会被正确设置。

只有 plugin skills 是 symlink,才会触发这个问题。


修复方案

代码改动

loadGeneratedPluginSkillRecords 函数中,为 plugin skill records 设置 syncSourceDir

function setSyncSourceForPluginSkill(
  record: LoadedSkillRecord,
  syncSourceDir: string,
): LoadedSkillRecord {
  return {
    ...record,
    syncSourceDir,
    syncDirName: path.basename(record.skill.baseDir),
  };
}

为什么不修改 baseDir

baseDir 会出现在 <available_skills> prompt 中,显示给 agent。如果改成真实路径,会暴露 host 文件系统结构。

因此:

  • baseDir 保持 symlink 路径(用于 display)
  • syncSourceDir 设置为真实路径(用于同步)

Real Behavior Proof

对于 sandbox 相关修复,证明需要覆盖完整的验证链条:

证明层面内容方式
环境正确Sandbox 容器运行docker ps
中间步骤正确Skills 同步到容器ls /workspace/skills/
最终结果正确文件可读head SKILL.md

证明步骤

# 1. 启动 sandbox 环境
node openclaw.mjs gateway
 
# 2. 触发 skill 加载
node openclaw.mjs tui --local
 
# 3. 验证容器运行
docker ps
 
# 4. 验证 skills 同步
docker exec <container_id> ls -la /workspace/skills/
 
# 5. 验证文件可读
docker exec <container_id> head -30 /workspace/skills/browser-automation/SKILL.md

ClawSweeper 审查

第一次提交的证明不完整(只有启动日志)。补充三层证明后:

rating: 🦞 diamond lobster ✨ media proof bonus
proof: sufficient

测试

新增测试文件,覆盖:

  1. Symlinked plugin skill 同步到 sandbox workspace
  2. 多个 plugin skill roots
  3. Escaped symlink targets 不被同步
  4. Sandbox prompt paths 不泄露 host 路径
Test Files: 2 passed (2)
Tests: 6 passed (6)

合并

Maintainer: steipete

Commit: d8f6d65


关键收获

Real Behavior Proof 的完整性

对于 sandbox 相关修复,只证明启动成功或测试通过是不够的。需要证明完整的验证链条。

Symlink 在 Sandbox 场景下的处理

Symlink 的特殊之处:

  • fs.realpath() 可以获取真实路径
  • fs.cp() 默认不解引用 symlink
  • 需要显式指定复制源为真实路径

相关链接


最后更新: 2026-05-27