[FEATURE] additionalSkillDirs in settings.json to let skills live alongside dotfiles

Status Fixed / completed
Maintainer reply None cached
Activity 1 comment · opened Aug 5, 2026 · closed Aug 5, 2026

Problem

I keep my Claude Code config versioned in a dotfiles repo and symlink it into ~/.claude/. Skills belong in that same repo — they're project-agnostic utilities I want version-controlled, diff-able, and shippable across machines. The only place the CLI discovers skills today is ~/.claude/skills/, which is a runtime directory I don't version-control.

Concrete use-cases:

  1. Author skills next to my dotfiles — I want ~/src/dotfiles/claude/skills/my-skill/SKILL.md to Just Work when I run claude without any extra flags or shell setup.
  2. Vendor a marketplace/plugin at a pinned commit — I want extraKnownMarketplaces but with a ref: field so I can pin to a specific tag or commit SHA and get reproducible installs (like mise.lock for dev tools or a go.sum). Today every claude plugins refresh pulls HEAD.

Proposed solution

1. additionalSkillDirs in settings.json

{
  "additionalSkillDirs": [
    "~/src/dotfiles/claude/skills"
  ]
}
  • Paths support ~ expansion and env-var substitution.
  • Skills discovered there are treated identically to ~/.claude/skills/ — same frontmatter, same slash-command registration, same scoping rules.
  • Relative paths resolved against ~/.claude/ for consistency with other settings fields.

This covers the dotfiles author case with zero extra shell config. Running claude picks up the skills automatically because settings.json (already symlinked from the dotfiles repo) declares them.

2. ref: field in extraKnownMarketplaces / enabledPlugins

{
  "extraKnownMarketplaces": {
    "my-skills": {
      "source": {
        "source": "git",
        "url": "git@github.com:me/my-skills.git",
        "ref": "v1.4.0"
      }
    }
  }
}
  • ref may be a branch, tag, or full commit SHA.
  • When ref is a SHA, plugins refresh is a no-op for that marketplace (already pinned).
  • When ref is a tag/branch, refresh resolves and re-pins to the current SHA of that ref, writing it back into settings.json (or a companion lockfile — see below).

Optional: skills lockfile (skills.lock)

Analogous to mise.lock / go.sum. Stores resolved commit SHAs for every marketplace so plugins refresh is deterministic on a fresh machine:

{
  "my-skills": { "ref": "v1.4.0", "sha": "abc123..." },
  "trailofbits": { "ref": "main", "sha": "def456..." }
}

A --locked flag (or a settings key plugins.requireLock: true) would make claude fail fast if skills.lock is missing or stale — the same pattern CI already uses for mise --locked.

Why not use plugins?

Plugins already support git-sourced marketplaces, but:

  • They install into ~/.claude/plugins/marketplaces/ (a runtime directory), not alongside the dotfiles.
  • There's no pinning — plugins refresh always pulls HEAD.
  • The extraKnownMarketplaces key in settings.json is the right hook, but it lacks a ref field.
  • Plugin discovery requires the marketplace to be registered and refreshed as a separate step; additionalSkillDirs would work without any extra step on a new machine.

Why not symlink?

Symlinking ~/src/dotfiles/claude/skills~/.claude/skills works for a single skills tree but:

  • Breaks if you want skills from multiple sources (dotfiles + a vendored marketplace).
  • ~/.claude/ is a runtime directory; symlinking into it creates a mixed ownership problem for tooling that writes there.
  • The symlink approach is invisible to settings.json-level config — it can't be declared portably alongside the rest of the config.

Affected areas

  • area:skills — skill discovery
  • area:plugins — marketplace pinning / lockfile
  • area:core — settings schema

Related issues

  • #25762 — CLAUDE_CONFIG_DIR env var (related: making the config location portable)
  • #75376 — skill visibility tiers / deferred loading
  • #76001 — hierarchical skill discovery

View original on GitHub ↗

This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗