nanocoai/nanoclaw · warning

Shared skill not symlinked: real entry occupies the path (te

Error message

Shared skill not symlinked: real entry occupies the path (template overlay or stale pre-refactor copy)

What it means

While syncing shared skills as symlinks into a group's skills directory, an existing real (non-symlink) entry occupies the path. This is either an intentional template overlay or a stale pre-refactor copy shadowing the shared skill (#3001); since no marker distinguishes them, the shared skill is skipped with a warning.

Source

Thrown at src/container-runner.ts:831

  }

  // Create symlinks for desired skills (container path targets)
  for (const skill of desired) {
    const linkPath = path.join(skillsDir, skill);
    let entry: fs.Stats | undefined;
    try {
      entry = fs.lstatSync(linkPath);
    } catch {
      /* missing */
    }
    if (!entry) {
      fs.symlinkSync(`/app/skills/${skill}`, linkPath);
    } else if (!entry.isSymbolicLink()) {
      // A real entry here is either a template overlay (intentional; see
      // src/group-skills.ts) or a stale pre-refactor skill copy that shadows
      // the shared skill (#3001). No marker distinguishes them yet, so
      // surface the skip instead of staying silent.
      log.warn(
        'Shared skill not symlinked: real entry occupies the path (template overlay or stale pre-refactor copy)',
        {
          skill,
          path: linkPath,
        },
      );
    }
  }
}

/**
 * Resolve the group's skill selection to concrete names — `'all'` recomputes
 * from `container/skills/` so newly-added upstream skills appear automatically.
 */
function selectedSkillNames(containerConfig: import('./container-config.js').ContainerConfig): string[] {
  if (containerConfig.skills !== 'all') return containerConfig.skills;
  const sharedSkillsDir = path.join(process.cwd(), 'container', 'skills');
  return fs.existsSync(sharedSkillsDir)

View on GitHub (pinned to 294ef2aee8)

Solutions

  1. If the local copy is stale (pre-refactor), delete it: `rm -rf groups/<folder>/skills/<skill>` so the symlink is created
  2. If it's an intentional template overlay, verify the local copy is current and ignore the warning
  3. Diff the local copy against `container/skills/<skill>` to decide which case it is

Example fix

rm -rf groups/my-group/skills/agent-browser # stale copy; next spawn symlinks the shared skill
Defensive patterns

Strategy: validation

Validate before calling

const entry = fs.lstatSync(linkPath, { throwIfNoEntry: false });
if (entry && !entry.isSymbolicLink()) {
  // decide: template overlay (keep) vs stale copy (delete) before spawning
}

Type guard

function isStaleSkillCopy(p: string): boolean {
  return fs.lstatSync(p).isSymbolicLink() === false && !isTemplateOverlay(p);
}

Prevention

When it happens

Trigger: `syncSkillSymlinks` (called from `buildMounts`) finds a real file/directory at `groups/<folder>/skills/<skill>` where a symlink to `/app/skills/<skill>` should go.

Common situations: A group created from a template that ships its own version of a shared skill; leftover skill copies from before the shared-mount refactor blocking updates to that skill.

Related errors


AI-assisted analysis of nanocoai/nanoclaw@294ef2aee8 (2026-08-28). Data as JSON: /api/errors/0959dcba373f90df. Report an issue: GitHub.