Issue #86190
Problem: Plugin skills couldn't be read by agents in sandbox mode.
Error:
Path escapes sandbox root: ~/.openclaw/plugin-skills/xxx/SKILL.md
Cause Analysis
Plugin Skills Storage
Plugin skills are stored in ~/.openclaw/plugin-skills/ as symlinks:
~/.openclaw/plugin-skills/browser-automation → /path/to/plugin/skills/browser-automation
Created by publishPluginSkills to expose plugin-provided skills.
Sandbox File Access
In sandbox mode, agents run in Docker containers. The read tool can only access /workspace directory.
syncSkillsToWorkspace copies skills to /workspace/skills/ for agent access.
Sync Failure Cause
const sourceDir = entry.syncSourceDir ?? entry.skill.baseDir;For plugin skills:
entry.skill.baseDir= symlink pathentry.syncSourceDir= undefined (not set)
Result: Attempts to copy from symlink path, which is a host path inaccessible to the container.
Why Other Skills Weren't Affected
Core skills are stored directly in ~/.openclaw/skills/ as real directories with syncSourceDir properly set.
Only plugin skills (symlinks) triggered this issue.
Fix
Code Change
Set syncSourceDir for plugin skill records in loadGeneratedPluginSkillRecords:
function setSyncSourceForPluginSkill(
record: LoadedSkillRecord,
syncSourceDir: string,
): LoadedSkillRecord {
return {
...record,
syncSourceDir,
syncDirName: path.basename(record.skill.baseDir),
};
}Why Not Modify baseDir
baseDir appears in <available_skills> prompt shown to agents. Using real path would expose host filesystem structure.
So:
baseDirkeeps symlink path (for display)syncSourceDirgets real path (for sync)
Real Behavior Proof
For sandbox-related fixes, proof needs complete verification chain:
| Proof Level | Content | Method |
|---|---|---|
| Environment correct | Sandbox container running | docker ps |
| Intermediate correct | Skills synced to container | ls /workspace/skills/ |
| Final correct | Files readable | head SKILL.md |
Proof Steps
# 1. Start sandbox environment
node openclaw.mjs gateway
# 2. Trigger skill loading
node openclaw.mjs tui --local
# 3. Verify container running
docker ps
# 4. Verify skills synced
docker exec <container_id> ls -la /workspace/skills/
# 5. Verify files readable
docker exec <container_id> head -30 /workspace/skills/browser-automation/SKILL.mdClawSweeper Review
First submission had incomplete proof (only startup logs). After adding three-layer proof:
rating: 🦞 diamond lobster ✨ media proof bonus
proof: sufficient
Tests
New test file covers:
- Symlinked plugin skill syncs to sandbox workspace
- Multiple plugin skill roots
- Escaped symlink targets not synced
- Sandbox prompt paths don't leak host paths
Test Files: 2 passed (2)
Tests: 6 passed (6)
Merge
Maintainer: steipete
Commit: d8f6d65
Key Learnings
Real Behavior Proof Completeness
For sandbox-related fixes, proving startup success or test pass isn't enough. Need complete verification chain.
Symlink Handling in Sandbox Scenarios
Symlink peculiarities:
fs.realpath()gets real pathfs.cp()doesn't dereference symlinks by default- Must explicitly specify real path as copy source
Related Links
Last updated: 2026-05-27