Resolution and precedence: which /deploy actually runs
By the end you can
- Name every location a skill can load from and which sessions each one reaches
- State which skill wins when a name is shared, including against bundled skills and command files
- Explain why a nested monorepo skill is missing at startup and how it becomes available
- Use skillOverrides' four states to hide or collapse a skill without editing its file
- Diagnose a skill that does not load, using /skills, claude plugin validate and --debug
- Say what a mid-session edit reaches, and the three cases it does not reach
You edit .claude/skills/deploy/SKILL.md, save, run /deploy, and get the old
behaviour. You read the file again. It is correct. You add a line saying "PRINT
THE WORD BANANA", run it again, and there is no banana.
The file you are editing is not the file that runs. Something else called
deploy is winning, and nothing told you. This lesson is which artefact wins,
why nobody warns you, and the one-command check that would have saved the last
twenty minutes.
Every place a skill can come from
Lesson 7.1 used two locations, personal and project. There are seven.
Where you save a skill decides which sessions load it. — Skills
| Location | Path | Loads in |
|---|---|---|
| Enterprise | .claude/skills/<name>/SKILL.md in the managed settings directory | Every machine your organisation deploys it to |
| Personal | ~/.claude/skills/<name>/SKILL.md | All your projects on this machine — not Cowork or cloud sessions |
| Project | .claude/skills/<name>/SKILL.md | Sessions in this repository; commit it and your team gets it |
| Nested | <subdir>/.claude/skills/<name>/SKILL.md | Sessions started in or below that subdirectory. A session started above it loads the skill once Claude works on files there |
| Additional directory | .claude/skills/<name>/SKILL.md in a --add-dir directory | That session only |
| Plugin | <plugin>/skills/<name>/SKILL.md | Wherever the plugin is enabled, as /plugin-name:skill-name |
| claude.ai account | Skills enabled in your claude.ai settings | Cowork and cloud sessions |
The personal row has a sting: a scheduled routine invoking a skill that lives
only in ~/.claude/skills/ reports the skill was not found, because each run
starts as a fresh remote session. Commit it to the repository, or ship it in a
plugin the repository's .claude/settings.json declares — a plugin you enabled
only in your own user settings does not transfer either.
Same name, one winner
Lesson 6.1 established that CLAUDE.md files are concatenated rather than
overridden: every discovered file arrives in the window and the later text merely
argues with the earlier text. Skills do not work that way. One file runs, the
rest are not consulted and not merged, and the docs describe no notice telling
you which one won — /skills is where you find out.
| Same name in | Which one runs |
|---|---|
| Two of enterprise, personal, and project | Enterprise over personal, and personal over project |
| Any of those and a bundled skill | Yours replaces the bundled command, but not its aliases |
A skill and a file in .claude/commands/ | The skill |
| A project-root skill and a nested skill | Both load |
| A plugin skill and a skill at any location above | Both load — plugin skills are namespaced |
| Any of the above and a skill synced from claude.ai | The other skill or command |
Read the first row as a winner, not a load order. The docs spell out the case that bit you:
With
deployin both~/.claude/skills/and the project's.claude/skills/,/deployruns the personal one. — Skills
A personal skill you wrote months ago, in a different repository, for a different
deployment, shadows the one your team reviewed. Nothing errors. /skills shows
each skill's source; that is the check to run before you read the body a second
time.
The alias row is the second surprise. A project code-review skill replaces
/code-review — but /review is an alias of it
(Commands), and the bundled alias
never runs yours. Half the team gets your version; the half that types the short
form gets the bundled one, with nothing at the prompt to tell them apart.
/review became that alias in v2.1.223. Before then it was a command of its own:
a single-pass, read-only review of a GitHub pull request by number, and from
v2.1.186 through v2.1.201 the same multi-agent engine as /code-review medium.
The split reached teams on those builds too, with a different explanation behind
it — a separate command rather than a second door onto the one you had replaced.
The rule that survives every version of this is the one to carry: before you take
a bundled name, look up its aliases.
Plugin skills sidestep the collision entirely. They are namespaced as
/plugin-name:skill-name, so /hello and /plugin-name:hello both remain
available rather than one overriding the other
(Plugins). Reach for a plugin when the
name you want is a generic one.
Monorepos: parents load, children wait
Discovery runs upward, not downward.
Claude Code loads project skills from
.claude/skills/in the directory where you start it and in every parent directory up to the repository root, so starting inpackages/frontend/still picks up skills defined at the root. — Skills
Downward is the asymmetry. A skill below where you started does not load at
startup. It loads the first time Claude reads or edits a file in that
subdirectory and stays for the rest of the session; until then it is absent from
the / menu and you cannot invoke it by name. That produces a bug report which
is not a bug — "the skill works sometimes" means it works after Claude has
happened to touch a file in apps/web/. Two fixes. /add-dir apps/web loads
that subdirectory's skills without waiting, which is the job /add-dir picked up
in v2.1.257; or start the session in the subdirectory, which picks up every
parent's skills on the way up anyway.
When a nested skill shares a name with a root one, both stay available. /deploy
runs the root skill, and Claude Code lists the directory-qualified variants for
Claude with an instruction to invoke the one whose directory holds the files it
is working on. You can type /apps/web:deploy to run the nested one yourself.
skillOverrides: four states, no file edit
Some skills you cannot or should not edit — one checked into a shared repo, or an
enterprise one. skillOverrides moves the visibility decision into settings.
| Value | Listed to Claude | In the / menu |
|---|---|---|
"on" | Name and description | Yes |
"name-only" | Name only | Yes |
"user-invocable-only" | Hidden | Yes |
"off" | Hidden | Hidden |
{
"skillOverrides": {
"legacy-context": "name-only",
"deploy": "off"
}
}A skill absent from skillOverrides is treated as "on", so you list only the
exceptions. "name-only" is a budget move — Lesson 7.5 showed the listing
costing context every turn, and this keeps the name while dropping the
description. "user-invocable-only" is Lesson 7.2's
disable-model-invocation: true imposed from outside the file: only you can run
it, with no diff in somebody else's repository. "off", as of v2.1.199, also
hides the skill from the command lists advertised to Remote Control clients and
Agent SDK callers, not just from your terminal.
You rarely type this JSON. The /skills menu writes it: highlight a skill, press
Space to cycle states, Esc to save — into .claude/settings.local.json,
which applies to you in this one project. Claude Code adds that file to your
global git excludes the first time it writes it, so the override stays out of
your commits (Settings); a
settings.local.json you created by hand is yours to gitignore. Three things
refuse to cycle there: plugin
skills, skills whose frontmatter already sets disable-model-invocation: true,
and skills whose skillOverrides entry comes from managed settings or the
--settings flag (Commands).
Editing a skill mid-session
Skill directories are watched, so the edit-run-edit loop needs no restart, for
skills under ~/.claude/skills/, the project .claude/skills/, or a
.claude/skills/ inside an --add-dir directory. Three limits: watching is off
in bare mode; it covers SKILL.md text only, so a skill folder that is also a
plugin needs /reload-plugins for changes to hooks/, .mcp.json, agents/ or
output-styles/; and a top-level skills directory created after startup is not
watched. That last one is the common case — your first project skill, where you
made .claude/skills/ and the skill in the same minute. Restart.
When it does not load at all
Work down this ladder; each step rules out a class of cause rather than guessing at one.
| Symptom | Likely cause | Check |
|---|---|---|
Not in /skills at all | Flat file instead of a folder | The layout is .claude/skills/name/SKILL.md |
Not in /skills, layout is right | Directory created after launch | Restart the session |
In /skills, /name works, Claude never picks it | Frontmatter did not parse | Run with --debug and read the parse error |
In /skills with a user-only badge | disable-model-invocation: true | Lesson 7.2 — working as written |
| Runs, but not your content | Another location wins | The source shown in /skills, which you can filter by |
The third row is the quiet one:
If the frontmatter YAML is malformed, Claude Code loads the skill body with empty metadata, so
/skill-namestill works but Claude has nodescriptionto match against. — Skills
A broken skill therefore behaves exactly like one with a badly written description — the failure Lesson 7.2 spent a section on. You would rewrite that description three times before suspecting a tab character. Sweep instead:
claude plugin validate .claude/skills
claude plugin validate ~/.claude/skillsIt reports skill, agent and command files whose frontmatter does not parse, and
looks only inside the directory you name (v2.1.233 or later). Naming .claude
checks skills, agents and commands in one run
(Plugin marketplaces).
When you cannot tell whether the problem is the skill or something else,
claude --safe-mode cuts everything out at once: CLAUDE.md, skills, plugins,
hooks, MCP servers and custom commands and agents all disabled
(Debug your configuration),
while authentication, model selection, built-in tools and permissions keep
working. If the problem disappears, one of your customisations causes it and the
table above finds which. If it persists, stop looking at your skills.
The order to check in
Everything above is a deterministic rule about files, not a judgement the model makes — which is why "ask Claude why my skill isn't running" is the worst available diagnostic. Ask the tooling, in order.
/skills— is it listed, and from which source?/reload-skills— re-scan the skill and command directories and read the count it reports, if the file is on disk and the menu disagrees. A top-level skills directory created after startup still needs a restart.claude plugin validate— does the frontmatter parse?--debug— what exactly did the parser say?--safe-mode— is it even my configuration?
Step 1 answers the /deploy question this lesson opened with, and it costs one
command. Run it before you read the file a second time.
Checkpoint
-
A teammate reports the project's
/code-reviewskill works for them and not for you, on the same commit. Name two explanations that fit, and the single command that separates them.Show answerAnswer
Either you have a personal
~/.claude/skills/code-review/shadowing the project one — personal wins over project, one file runs, the rest are not consulted and nothing errors — or your ownskillOverrideshas that name set:"off"hides it from your/menu,"user-invocable-only"hides it from the listing Claude sees while leaving it in yours. Neither touches the repository./skillsseparates them in one command: it shows each skill's source, which you can filter by, and the state you have it cycled to. Same commit is exactly why the difference has to be outside the commit. One variant worth ruling out at the prompt: if you have been typing/review, you are on a bundled alias, and an alias never runs your version of the skill. -
You start Claude Code at a monorepo root and
/deployis missing from the/menu, thoughapps/web/.claude/skills/deploy/SKILL.mdexists. Is this broken? What are your two options?Show answerAnswer
Not broken — this is discovery running upward and not downward. Claude Code loads project skills from the directory you start in and every parent up to the repository root, so a skill below where you started is absent at startup and cannot be invoked by name; it loads the first time Claude reads or edits a file in that subdirectory and then stays for the session. That is what produces the "works sometimes" bug report that is not a bug.
Option one:
/add-dir apps/web, which loads that subdirectory's skills without waiting — a job/add-dirpicked up in v2.1.257. Option two: start the session inapps/web, which also picks up every parent's skills on the way up, so you lose nothing from the root. -
A skill checked into a shared repo keeps firing on requests it should not, and the file is not yours to edit. What do you change, what state do you set, and which file does it get written to?
Show answerAnswer
Change
skillOverrides, set that skill to"user-invocable-only", and it is written to.claude/settings.local.json. That state hides the skill from the listing Claude sees while leaving it in your/menu — Lesson 7.2'sdisable-model-invocation: trueimposed from outside the file, with no diff in somebody else's repository."off"would go further than the problem asks, as it hides the skill from you too.You do not type the JSON: the
/skillsmenu writes it — highlight the skill,Spaceto cycle states,Escto save. The file applies to you in this one project, and Claude Code adds it to your global git excludes the first time it writes it, so the override stays out of your commits. If the name refuses to cycle, check whether it is a plugin skill —skillOverridesdoes not reach those, and/pluginis where they are managed. -
Your skill appears in
/skills,/my-skillruns it correctly, and Claude never invokes it on its own. Which two causes fit, and which command tells you which one it is?Show answerAnswer
Malformed frontmatter, or a
descriptionthat does not match how you phrase requests. They are indistinguishable from where you are standing: if the YAML is malformed, Claude Code loads the skill body with empty metadata, so/my-skillstill works — the command comes from the directory — while Claude has no description to match against. A broken skill behaves exactly like a badly written one, and you would rewrite that description three times before suspecting a tab character.Sweep with
claude plugin validate .claude/skills(or~/.claude/skills), which reports skill, agent and command files whose frontmatter does not parse;--debugthen gives you the parser's exact complaint. If it parses cleanly, the description is the problem. And if/skillsshows auser-onlybadge, it is neither — that isdisable-model-invocation: trueworking as written.