Skip to main content
Claude Code Skills in Practice: How We Use SKILL.md, Invocation Control, and a Leaner CLAUDE.md

Claude Code Skills in Practice: How We Use SKILL.md, Invocation Control, and a Leaner CLAUDE.md

November 6, 2026
AI & Dev Tools
9 min read

Key takeaways

A Claude Code skill is a directory with a SKILL.md file: YAML frontmatter that says when to use it, followed by Markdown instructions. The directory name becomes the slash command.

Only a skill's description sits in context by default; the body loads when someone types /skill-name or Claude matches the description, so long reference material costs almost nothing until it is used.

Custom commands are merged into skills: .claude/commands/deploy.md and .claude/skills/deploy/SKILL.md both create /deploy, and the skill format adds supporting files and invocation control.

disable-model-invocation: true stops Claude from triggering a skill on its own, which is the setting we use for deploys, commits and anything else with side effects.

Claude Code does not re-read an invoked skill on later turns, and after auto-compaction it keeps only the first 5,000 tokens of each re-attached skill, so critical instructions belong at the top.

Why did we move procedures out of CLAUDE.md?

At Devya our CLAUDE.md files had turned into runbooks. Deploy steps, release checklists, publishing routines and long API-convention sections loaded into every Claude Code session, including sessions where an engineer only wanted to rename a variable. The Claude Code documentation names that exact symptom as the signal to create a skill: when a section of CLAUDE.md has grown into a procedure rather than a fact, it should move out. Our team has since moved most repeated workflows, deploys especially, into skills.

What is a Claude Code skill, and how is it different from CLAUDE.md?

A Claude Code skill is a directory containing a SKILL.md file that packages one procedure or one body of knowledge for Claude to load on demand. YAML frontmatter between --- markers tells Claude when the skill applies, and the Markdown body holds the instructions Claude follows once the skill runs. Skills follow the Agent Skills open standard published at agentskills.io; Claude Code extends it with invocation control, subagent execution and dynamic context injection.

The practical difference from CLAUDE.md is when the text costs tokens. CLAUDE.md loads in full into every session. A skill contributes only its name and description to the skill listing, and its body enters the conversation when it is invoked. Our rule: CLAUDE.md holds short facts true in every session, such as the package manager, the test command and the canonical domain, and anything shaped like a numbered procedure becomes a skill.

A typical skill of ours is a folder such as .claude/skills/release-notes/ holding SKILL.md, a template.md that defines the output format, and a scripts/ folder. The frontmatter sets name: release-notes and a description that starts with what the skill does (drafts release notes from merged PRs since the last tag) and ends with the phrases people actually type (release notes, a changelog entry, what shipped this week).

Where do skills live, and which one wins a name clash?

Personal skills live in ~/.claude/skills/skill-name/SKILL.md and apply to every project on one machine.

Project skills live in .claude/skills/skill-name/SKILL.md and reach everyone who clones the repository once committed.

Nested skills live in a subdirectory's own .claude/skills/ folder and load the first time Claude reads or edits a file in that subdirectory.

Enterprise skills live in the managed settings directory and reach every user on machines the organization manages.

When two levels define the same name, enterprise beats personal and personal beats project, and a skill beats a .claude/commands/ file with the same name. Because a personal skill wins over a project skill, a personal /deploy would shadow a committed project /deploy in every repository, so our engineers prefix personal skills with the project name, as in projectname-deploy.

How do we write a skill description Claude actually matches?

The description field decides whether a skill ever runs without being typed, so we write it like a search query: what the skill does, then the phrases a person would actually say. Claude Code truncates the combined description and when_to_use text at 1,536 characters in the skill listing, so the key use case goes in the first sentence. A description such as “Helps with releases” never matches “what shipped this week?”; one that names release notes, changelog entries and what shipped does.

The skill listing has its own budget. Claude Code always lists every skill name, but when the listing exceeds its character budget, which scales at 1% of the model's context window, it drops descriptions starting with the least-invoked skills. /doctor estimates the listing's context cost, and the skillListingBudgetFraction setting raises the budget.

Malformed YAML looks exactly like a vague description: Claude Code loads the body with empty metadata, so /skill-name still works but Claude has nothing to match against. claude plugin validate .claude/skills (Claude Code v2.1.233 or later) reports frontmatter that does not parse.

Who should be allowed to invoke a skill: the developer or Claude?

By default both the developer and Claude can invoke any skill, and two frontmatter fields restrict that.

Default: the developer can invoke it, Claude can invoke it, and the description is always in context.

disable-model-invocation: true: only the developer can invoke it, and the description stays out of context until then. We set it on every skill with side effects: deploys, commits, publishing and outgoing messages.

user-invocable: false: only Claude can invoke it, and the description is always in context. It suits background knowledge, such as how a legacy module works, where typing a slash command is not a meaningful action.

A manual-only skill therefore costs no listing budget. If Claude tries to call one anyway, Claude Code blocks the call and tells Claude not to reproduce the steps another way. Our deploy skills combine disable-model-invocation: true with an allowed-tools list that pre-approves the test and build commands; that grant applies only to the turn that invokes the skill and clears when the next message is sent.

How do we feed live data into a skill?

Dynamic context injection runs a shell command before Claude sees the skill and replaces the line with the command's output. A line made of an exclamation mark followed by a command in backticks, such as gh pr diff, is how our review skills arrive with the current diff already inlined, instead of spending a tool call to fetch it.

A failed command aborts the whole invocation, not just its own placeholder. Any non-zero exit counts as a failure except exit code 1 from search and comparison commands such as grep, so we append || true to check scripts that exit 1 when they find problems.

Injected commands never prompt for permission. Claude Code checks each one against the permission rules, and outside auto mode anything other than an allow aborts the invocation, so we pre-approve every injected command in allowed-tools.

Arguments arrive as placeholders: $ARGUMENTS holds the full argument string, and $0, $1 and so on hold positional values with shell-style quoting. Adding context: fork with agent: Explore runs a skill in a separate subagent that does not see the conversation history, so a forked skill's instructions must stand on their own.

Why does Claude stop following a skill halfway through a session?

Claude Code inserts an invoked skill's content into the conversation once and does not re-read SKILL.md on later turns. A one-time step such as “run the tests” applies once, while a standing rule such as “run the tests after every edit” keeps applying.

Auto-compaction is the second cause. When Claude Code summarizes a long conversation, it re-attaches the most recent invocation of each skill but keeps only its first 5,000 tokens, and all re-attached skills share a combined 25,000-token budget filled from the most recently invoked skill backwards. We keep SKILL.md under the documented 500-line guideline, put non-negotiable instructions in the first screen, and move reference material into linked supporting files.

A rule that must hold on every action belongs in a hook, not a skill. A Claude Code hook runs on every matching event, such as before each file edit, whether or not Claude is following anything; a skill can also declare hooks in its hooks frontmatter field, active from invocation until the session ends.

When should a rule be CLAUDE.md, a skill, a hook, or a subagent?

CLAUDE.md loads in full every session and holds short facts true in every task: commands, conventions, domains.

A skill loads its description always and its body on invoke, and holds procedures and reference material: deploys, release notes, API conventions.

A hook runs on matching events outside the model and enforces deterministic rules: blocking edits to .env files, running the formatter.

A subagent in .claude/agents/ runs in its own context window and suits isolated research or review that should return only a summary.

Our migration test for any CLAUDE.md line is two questions. If deleting the line would matter only during one kind of task, it moves to a skill. If Claude ignoring it once would cause damage, it becomes a hook. Whatever survives both stays in CLAUDE.md.

FAQ

Q: Do .claude/commands/ files still work now that commands are merged into skills?

A: Yes. .claude/commands/deploy.md and .claude/skills/deploy/SKILL.md both create /deploy and accept the same frontmatter except name and paths; when both exist, the skill wins. New work should use the skill format because only skills support supporting files.

Q: Does a Claude Code skill cost tokens if nobody uses it?

A: Only its description, which sits in the skill listing so Claude knows the skill exists. The body loads only when invoked, and a skill with disable-model-invocation: true keeps even its description out of context.

Q: Do personal skills load in cloud sessions and scheduled routines?

A: No. Cloud sessions and cloud scheduled tasks start fresh and do not read ~/.claude/skills/ on a developer's machine, so a skill they need must be committed to the repository's .claude/skills/ or enabled on the claude.ai account. Desktop scheduled tasks run locally and do load personal skills.