How the Skills CLI Differentiates Between Symlink and Copy Installation Modes

The skills CLI distinguishes between symlink and copy installation modes through explicit --mode flags, with symlink as the default that gracefully falls back to copy when filesystem permissions block symlink creation.

The skills CLI from vercel-labs/skills provides two fundamentally different ways to install AI agent skills: symlink mode creates a central canonical copy with agent-specific links, while copy mode duplicates files directly into each agent directory. Understanding how the skills CLI differentiates between symlink and copy installation modes helps you optimize for development workflows, disk space, and cross-platform compatibility.

The Core Distinction: Canonical Storage vs. Direct Duplication

The skills CLI's installation mode differentiation centers on one architectural decision: whether to maintain a canonical centralized copy of each skill.

In symlink mode, the CLI:

  1. Copies skill files once to a canonical directory at ~/.agents/skills/<skill>
  2. Creates a filesystem symlink from the agent-specific directory (e.g., ~/.claude/skills/<skill>) to that canonical location
  3. Falls back automatically to copy mode if symlink creation fails, recording symlinkFailed: true

This mode is selected when no --mode flag is provided, or when explicitly passing --mode symlink.

Copy Mode

In copy mode, the CLI:

  • Bypasses the canonical directory entirely
  • Copies skill files directly into each agent-specific directory
  • Creates no symlinks whatsoever

This mode is selected via --mode copy or -m copy.

Where the Differentiation Logic Lives

The skills CLI differentiates between symlink and copy installation modes in src/installer.ts, with four key implementation points.

1. Type Definition

The valid modes are strictly typed:

export type InstallMode = 'symlink' | 'copy';

Source: src/installer.ts lines 23-24

2. Default Resolution

All installation entry points—installSkillForAgent, installRemoteSkillForAgent, installWellKnownSkillForAgent, and installBlobSkillForAgent—resolve the mode with a simple fallback:

const installMode = options.mode ?? 'symlink';

Source: src/installer.ts lines 43-45

3. Copy Mode Branch

When installMode === 'copy', the code takes an early return path that skips all canonical directory logic:

if (installMode === 'copy') {
  await cleanAndCreateDirectory(agentDir);
  await copyDirectory(skill.path, agentDir);
  return { success: true, path: agentDir, mode: 'copy' };
}

Source: src/installer.ts lines 64-71

The symlink path first establishes the canonical copy, then attempts symlink creation:

await cleanAndCreateDirectory(canonicalDir);
await copyDirectory(skill.path, canonicalDir);
// ...
const symlinkCreated = await createSymlink(canonicalDir, agentDir);

If createSymlink fails, the CLI automatically falls back to copy mode while preserving the canonical path information:

if (!symlinkCreated) {
  await cleanAndCreateDirectory(agentDir);
  await copyDirectory(skill.path, agentDir);
  return {
    success: true,
    path: agentDir,
    canonicalPath: canonicalDir,
    mode: 'symlink',
    symlinkFailed: true,
  };
}

Source: src/installer.ts lines 92-107

Special Case: Universal Agents

The skills CLI includes an optimization for universal agents like Claude and Cursor. When installing globally for these agents, the canonical directory is the agent directory, making symlinks redundant.

This check occurs in src/installer.ts:

if (isGlobal && isUniversalAgent(agentType)) {
  return { success: true, path: canonicalDir, canonicalPath: canonicalDir, mode: 'symlink' };
}

Source: src/installer.ts lines 81-84

The isUniversalAgent determination comes from src/agents.ts, which defines which agent types support global skill sharing.

Practical Usage Examples


# Installs skill with symlink to canonical location

skills add my-skill

Explicit Copy Mode


# Bypasses canonical directory, copies files directly

skills add my-skill --mode copy

# or

skills add my-skill -m copy

The CLI returns structured results that reveal when fallback occurred:

{
  "success": true,
  "path": "/home/user/.claude/skills/my-skill",
  "canonicalPath": "/home/user/.agents/skills/my-skill",
  "mode": "symlink",
  "symlinkFailed": true
}

When symlinkFailed: true appears, the files were copied directly despite the mode: "symlink" designation—typically due to Windows without Developer Mode or insufficient filesystem permissions.

Summary

  • Symlink mode (default) creates a canonical copy at ~/.agents/skills/<skill> and links agent directories to it, falling back to copy automatically on failure
  • Copy mode bypasses the canonical directory entirely, duplicating files directly into agent-specific locations
  • The differentiation logic resides primarily in src/installer.ts, with type definitions in src/types.ts and agent classification in src/agents.ts
  • Universal agents like Claude and Cursor receive optimized handling where the canonical directory equals the agent directory, eliminating redundant symlinks

Frequently Asked Questions

Pass the --mode copy or -m copy flag to any skills add command. The CLI will bypass the canonical directory and copy files directly to the agent-specific location.

The skills CLI automatically detects symlink creation failures—common on Windows without Developer Mode—and falls back to copy mode. The result includes symlinkFailed: true so you can identify when this occurred.

Where does the canonical directory get created?

The canonical directory defaults to ~/.agents/skills/<skill-name> on Unix systems, with equivalent paths on Windows. This location is controlled by constants in src/constants.ts defining AGENTS_DIR and SKILLS_SUBDIR.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →