CCLearn

Resolution and precedence: which /deploy actually runs

verified against Claude Code 2.1.261 · 2026-09-0914 min

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

LocationPathLoads in
Enterprise.claude/skills/<name>/SKILL.md in the managed settings directoryEvery machine your organisation deploys it to
Personal~/.claude/skills/<name>/SKILL.mdAll your projects on this machine — not Cowork or cloud sessions
Project.claude/skills/<name>/SKILL.mdSessions in this repository; commit it and your team gets it
Nested<subdir>/.claude/skills/<name>/SKILL.mdSessions 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 directoryThat session only
Plugin<plugin>/skills/<name>/SKILL.mdWherever the plugin is enabled, as /plugin-name:skill-name
claude.ai accountSkills enabled in your claude.ai settingsCowork 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 inWhich one runs
Two of enterprise, personal, and projectEnterprise over personal, and personal over project
Any of those and a bundled skillYours 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 skillBoth load
A plugin skill and a skill at any location aboveBoth load — plugin skills are namespaced
Any of the above and a skill synced from claude.aiThe 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 deploy in both ~/.claude/skills/ and the project's .claude/skills/, /deploy runs 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 in packages/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.

ValueListed to ClaudeIn the / menu
"on"Name and descriptionYes
"name-only"Name onlyYes
"user-invocable-only"HiddenYes
"off"HiddenHidden
json
{
  "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.

SymptomLikely causeCheck
Not in /skills at allFlat file instead of a folderThe layout is .claude/skills/name/SKILL.md
Not in /skills, layout is rightDirectory created after launchRestart the session
In /skills, /name works, Claude never picks itFrontmatter did not parseRun with --debug and read the parse error
In /skills with a user-only badgedisable-model-invocation: trueLesson 7.2 — working as written
Runs, but not your contentAnother location winsThe 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-name still works but Claude has no description to 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:

bash
claude plugin validate .claude/skills
claude plugin validate ~/.claude/skills

It 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.

  1. /skills — is it listed, and from which source?
  2. /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.
  3. claude plugin validate — does the frontmatter parse?
  4. --debug — what exactly did the parser say?
  5. --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

  1. A teammate reports the project's /code-review skill works for them and not for you, on the same commit. Name two explanations that fit, and the single command that separates them.

    Show answer

    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 own skillOverrides has 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.

    /skills separates 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.

  2. You start Claude Code at a monorepo root and /deploy is missing from the / menu, though apps/web/.claude/skills/deploy/SKILL.md exists. Is this broken? What are your two options?

    Show answer

    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-dir picked up in v2.1.257. Option two: start the session in apps/web, which also picks up every parent's skills on the way up, so you lose nothing from the root.

  3. 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 answer

    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's disable-model-invocation: true imposed 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 /skills menu writes it — highlight the skill, Space to cycle states, Esc to 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 — skillOverrides does not reach those, and /plugin is where they are managed.

  4. Your skill appears in /skills, /my-skill runs it correctly, and Claude never invokes it on its own. Which two causes fit, and which command tells you which one it is?

    Show answer

    Malformed frontmatter, or a description that 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-skill still 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; --debug then gives you the parser's exact complaint. If it parses cleanly, the description is the problem. And if /skills shows a user-only badge, it is neither — that is disable-model-invocation: true working as written.

Sources