[FEATURE] additionalSkillDirs in settings.json to let skills live alongside dotfiles
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:
- Author skills next to my dotfiles — I want
~/src/dotfiles/claude/skills/my-skill/SKILL.mdto Just Work when I runclaudewithout any extra flags or shell setup. - Vendor a marketplace/plugin at a pinned commit — I want
extraKnownMarketplacesbut with aref:field so I can pin to a specific tag or commit SHA and get reproducible installs (likemise.lockfor dev tools or ago.sum). Today everyclaude plugins refreshpulls 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"
}
}
}
}
refmay be a branch, tag, or full commit SHA.- When
refis a SHA,plugins refreshis a no-op for that marketplace (already pinned). - When
refis a tag/branch, refresh resolves and re-pins to the current SHA of that ref, writing it back intosettings.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 refreshalways pulls HEAD. - The
extraKnownMarketplaceskey insettings.jsonis the right hook, but it lacks areffield. - Plugin discovery requires the marketplace to be registered and refreshed as a separate step;
additionalSkillDirswould 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 discoveryarea:plugins— marketplace pinning / lockfilearea:core— settings schema
Related issues
- #25762 —
CLAUDE_CONFIG_DIRenv var (related: making the config location portable) - #75376 — skill visibility tiers / deferred loading
- #76001 — hierarchical skill discovery
This issue has 1 comment on GitHub. Read the full discussion on GitHub ↗