Back to blog

OpenClaw PR #86261: Plugin Skills Sandbox Sync Fix

Plugin skills couldn't be read by agents in sandbox mode because symlink syncSourceDir was unset. Fix earned diamond lobster rating.

#OpenClaw#Open Source#PR#Sandbox#Symlink

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 path
  • entry.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:

  • baseDir keeps symlink path (for display)
  • syncSourceDir gets real path (for sync)

Real Behavior Proof

For sandbox-related fixes, proof needs complete verification chain:

Proof LevelContentMethod
Environment correctSandbox container runningdocker ps
Intermediate correctSkills synced to containerls /workspace/skills/
Final correctFiles readablehead 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.md

ClawSweeper 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:

  1. Symlinked plugin skill syncs to sandbox workspace
  2. Multiple plugin skill roots
  3. Escaped symlink targets not synced
  4. 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 path
  • fs.cp() doesn't dereference symlinks by default
  • Must explicitly specify real path as copy source

Related Links


Last updated: 2026-05-27