Claude Code skills don’t do subfolders — here’s what to do instead
I had 34 Claude Code skills sitting in one flat folder. They were symlinked into ~/.claude/skills straight out of my dotfiles repo, so…
Claude Code skills don’t do subfolders — here’s what to do instead

I had 34 Claude Code skills sitting in one flat folder. They were symlinked into ~/.claude/skills straight out of my dotfiles repo, so editing a skill meant editing the repo and the change went live on the next session. Nice and simple.
The names had grown ugly, though. coding-conventions, coding-tdd, second-brain-ingest, swiftui-pro, swift-testing-pro. The prefixes were the only grouping I had. They weren't categories, just a naming convention I'd talked myself into.
So I asked what felt like an obvious question: can I just make subfolders? Drop tdd into a coding/ folder, ingest into second-brain/, and let the folder be the category?
The answer is no. And chasing why it’s no led me somewhere much better.
A quick note on scope. This is written for people who manage their own Claude Code setup out of a dotfiles repo and want their skills grouped without giving up live edits. If you’re a team packaging skills for other people to install, you want a marketplace and most of what follows won’t apply to you. And because plugin specifics move fast: everything here was verified on Claude Code 2.1.170, so check your own version as you read.
The dead end
Claude Code discovers skills flat. It walks the skills directory, and for each folder that holds a SKILL.md, the folder name becomes the invocation name. That's it. There's no recursion into category subfolders, no notion of a parent group.
So skills/coding/tdd/SKILL.md does not give you a coding category with a tdd skill inside it. At best the discovery just doesn't find it. My naive mental model (folders are categories) simply isn't the model the tool uses.
That’s annoying, but it’s also a useful forcing function. If the filesystem won’t carry the category, something else has to. The question becomes: what’s the actual mechanism for grouping skills, and does it survive a symlinked dotfiles setup?
Three ways Claude Code loads skills
It turns out there are three, and they’re easy to conflate. Here’s the whole picture:

Why the marketplace is a trap for personal setups
This is the counterintuitive part, so it’s worth being concrete about it.
When you install a plugin from a marketplace, Claude Code copies it into ~/.claude/plugins/cache/. Your repo is no longer the live source. Edit a skill in your dotfiles and nothing happens until you run /plugin marketplace update. For a collection I tweak constantly, that copy step quietly kills the one property I cared about most.
It gets worse for a dotfiles bootstrap. Adding a marketplace and installing from it trigger interactive trust prompts. On a fresh machine, where I want a non-interactive script to lay everything down, those prompts block the whole thing.
And none of that ceremony buys me anything. Marketplaces exist to distribute to other people and to pin versions. I’m distributing to exactly one person, me, across machines I already trust. I’m paying for publishing infrastructure I don’t use.
The skills-directory plugin, concretely
Here’s the mechanism that does what I want.
Any folder directly under a skills directory that contains a .claude-plugin/plugin.json loads as a plugin on the next session. No install, no registration, no trust prompt. (claude plugin init <name> scaffolds the manifest if you don't want to write it by hand.)
The namespace comes from the name field in plugin.json. A plugin named coding exposes its skills as coding:tdd, coding:conventions, and so on. Same for agents: coding:pr-reviewer.
And it’s a full plugin, not a skills-only thing. One bucket can carry skills and agents and commands and hooks, all namespaced together. That last part matters more than I expected. My agents could move inside the same bucket as the skills they relate to.
Because discovery happens in place rather than by copying, the symlink workflow survives intact. Symlink the bucket into ~/.claude/skills/, and Claude Code follows the link and reads the files where they really live. Live edits work.
The dotfiles change is one line
# before: one symlink for everything, flat, no namespaces
ln -sfn "$DOTFILES/DragonAgents/skills" ~/.claude/skills
ln -sfn "$DOTFILES/DragonAgents/agents" ~/.claude/agents
# after: point at the buckets' parent; each bucket auto-loads, namespaced.
# agents now live inside their buckets, so the agents symlink goes away.
ln -sfn "$DOTFILES/DragonAgents/plugins" ~/.claude/skills
Two symlinks become one. The agents symlink disappears entirely because agents are part of the buckets now.
I didn’t trust the docs, so I ran a smoke test
The behaviour above is the kind of thing that’s easy to get subtly wrong, especially the “agents load too” and “symlinks are followed” claims. Before migrating 34 skills, I built a throwaway bucket and checked.
# bucket layout:
# coding/.claude-plugin/plugin.json
# coding/skills/hello/SKILL.md
# coding/agents/echo-bot.md
ln -sfn /tmp/.../coding ~/.claude/skills/smoketest-coding
claude plugin list
# -> smoketest-coding@skills-dir, enabled, installPath = the symlink
claude plugin details 'smoketest-coding@skills-dir'
# Component inventory: Skills (1) hello | Agents (1) echo-bot
# Projected token cost: always-on ~78 tok
That confirmed all three things at once: discovery works through a symlink, it reads in place rather than copying, and agents load from a skills-directory plugin, not just skills.
Two things I learned here are worth keeping:
First, claude plugin list and claude plugin details run as fresh processes. They trigger discovery on their own, so you can verify a newly-dropped plugin without restarting your running session.
Second, claude plugin details reports a projected token cost per component: always-on versus on-invoke. That's a real budgeting tool. It tells you how much context each bucket costs you just by existing, before you've invoked anything.
Migrate less: the dedup pass
Here’s the part I almost skipped, and I’m glad I didn’t. Before moving anything, I shrank the surface area.
The repo had the same capability expressed three different ways. Take code review: there was a review skill, a pr-review command, and a pr-reviewer agent. The skill and the agent carried near-identical bodies. They'd already drifted apart, so they didn't even agree with each other anymore.
That’s the teachable moment, because it forces you to be honest about what each of these things is actually for:
- A skill is a reusable procedure or piece of expertise. It runs inline in your context, and it auto-triggers from its description.
- An agent (subagent) is a separate context window with its own tool sandbox. You reach for it when you need isolation (its file-reading doesn’t bloat your main context, only its summary comes back), tool restriction (read-only, say), or parallel fan-out.
- A command is a saved prompt behind a typed trigger. Pure glue. And it’s largely superseded by skills, which can both auto-trigger and be invoked as
/name.
Commands and agents predate skills. That’s why old setups like mine accumulate redundant pairs: I built the agent before the skill existed, then built the skill and never deleted the agent.
The fix is a clean, reusable pattern. Keep the procedure in the skill, and make the agent a thin wrapper that loads the skill and adds only what’s genuinely its own: the isolation, the tool limits, an output override. My oss-scout agent is the example of this done right: about 20 lines that just run the oss-research skill in a subagent. The procedure lives in one place.
I went through and deleted:
- 4 slash commands, thin launchers I never actually typed.
- 1 agent (
pr-prepper) that nothing invoked. - 2 skills (
second-brain-lookup,second-brain-propose) that were orphaned. The three agents they'd been built for had been removed in an earlier migration, so nothing called them anymore.
That last cut is the one I want to underline. Those two skills weren’t obsolete. The audit found nothing technically wrong with them. They were just dead by usage: no agent invoked them, so they were weight with no lever. A well-designed skill you never call is still dead weight. Trim by usage, not only by obsolescence.
The net: ~34 skills, 6 agents, 4 commands became 32 skills, 5 thin agents, 0 commands, with each capability’s procedure living in exactly one place.
The payoff
What I ended up with is six namespaced buckets, each a skills-directory plugin: coding, swift, planning, writing, obsidian, second-brain.
Here's the shape of it on disk:
DragonAgents/plugins/ # ← the one symlink, into ~/.claude/skills
├── coding/
│ ├── .claude-plugin/plugin.json # name: "coding" → coding:*
│ ├── skills/
│ │ ├── tdd/SKILL.md # → coding:tdd
│ │ ├── conventions/SKILL.md # → coding:conventions
│ │ └── review/SKILL.md # → coding:review
│ └── agents/
│ ├── pr-reviewer.md # → coding:pr-reviewer
│ └── oss-scout.md # thin wrapper over oss-research
├── swift/
│ ├── .claude-plugin/plugin.json # name: "swift" → swift:*
│ └── skills/
│ ├── ui/SKILL.md # → swift:ui (was swiftui-pro)
│ └── testing/SKILL.md # → swift:testing (was swift-testing-pro)
├── planning/
├── writing/
├── obsidian/
└── second-brain/
Invocation now reads the way I always wanted it to. `coding:tdd`. `second-brain:ingest`. `swift:ui`. The namespace carries the category, so the old prefixes just fall away (`coding-conventions` becomes `coding:conventions`), and the Swift skills get normalized along the way: `swiftui-pro` to `swift:ui`, `swift-testing-pro` to `swift:testing`.
The grouping *is* a namespace now, instead of a naming convention I was enforcing by hand. That’s the whole difference. The category used to be a string I prepended and hoped everyone respected. Now it’s structural, and the tool enforces it for me.
I wrote the decision down as an ADR in the repo, so future-me doesn’t re-litigate the marketplace question from scratch in six months.
# A few honest caveats
- Plugin specifics move fast (I verified against 2.1.170, noted up top). Date your post and check your own version before trusting the exact command output.
- Skills-directory plugins are the right call for *personal* use. If you’re genuinely distributing to other people, a marketplace is correct. That’s what the versioning, trust, and discovery machinery is *for*.
- Renames have a cost. Agents reference skills by name, so when you rename a skill you have to update those references in lockstep or you’ll break the wiring. Do them together.
If you’ve got a flat pile of skills and you’ve felt that prefix-naming itch, this is the way out. You don’t need a marketplace. You need a manifest and one changed symlink.
I hope this saves you the dead end I walked into 😉 메타데이터
- post_id
- aa98cdbc4b95
- slug
- claude-code-skills-dont-do-subfolders-here-s-what-to-do-instead-aa98cdbc4b95
- url
- https://medium.com/arconsis/claude-code-skills-dont-do-subfolders-here-s-what-to-do-instead-aa98cdbc4b95
- canonical_url
- https://medium.com/arconsis/claude-code-skills-dont-do-subfolders-here-s-what-to-do-instead-aa98cdbc4b95
- author_url
- https://medium.com/@thimo.bess
- status
- ok
- fetched_at
- 2026-07-19 18:36:54