A Claude Code skill is a folder holding a SKILL.md, at .claude/skills/<skill-name>/SKILL.md in a project or ~/.claude/skills/<skill-name>/SKILL.md for you alone, and you run it as /skill-name. A custom slash command is a Markdown file in .claude/commands/, and the skills page now says “Custom commands have been merged into skills.” A subagent is a Markdown file in .claude/agents/ or ~/.claude/agents/ whose frontmatter must carry name and description. To check all three before you open a session, run claude plugin validate .claude, and add --strict if a warning should fail the run.

Everything in this table comes from the skills page and the subagents page, read on 2026-10-13:

SkillCustom slash commandSubagent
Project location.claude/skills/<skill-name>/SKILL.md.claude/commands/.claude/agents/
Personal location~/.claude/skills/<skill-name>/SKILL.mdnot given on the skills page~/.claude/agents/
Example file.claude/skills/deploy/SKILL.md.claude/commands/deploy.md.claude/agents/code-reviewer.md
The name you typethe folder name, or the name fieldthe file name without extensionthe name field
You run it with/skill-name/deploy@agent-<name>
Required frontmatternonenonename and description
A multi-word fieldallowed-toolsallowed-toolsdisallowedTools

The frontmatter fields, in full, as the two pages list them:

  • Skill: “All fields are optional. Only description is recommended”. The rest are name, when_to_use, argument-hint, arguments, disable-model-invocation, user-invocable, allowed-tools, disallowed-tools, model, effort, context, agent, background, hooks, paths, shell, metadata, license and compatibility. Field names use “lowercase words separated by hyphens, except when_to_use”.
  • Custom slash command: “the same fields except name and paths”.
  • Subagent: “Only name and description are required.” The optional ones are tools, disallowedTools, model, permissionMode, maxTurns, skills, mcpServers, hooks, memory, background, omitClaudeMd, effort, isolation, color, initialPrompt and experimental, and “Multi-word field names use camelCase”.

Besides typing a name, “Claude uses skills when relevant”, and a subagent can also be named in a prompt or run for a whole session with --agent <name>. The .claude/agents/code-reviewer.md path is the subagents page’s own example; “The filename doesn’t have to match.”

Two details there catch people out. Skill fields are hyphenated (allowed-tools) and subagent fields are camelCase (disallowedTools), and both pages warn that “Claude Code ignores a field it doesn’t recognize without reporting an error.” And a subagent file with no name is not an error to Claude Code at all: it “treats the file as documentation kept beside your agents.”

How commands and skills relate, in the docs’ words

There is no separate slash-commands page any more. On 2026-10-13 the .md copy of code.claude.com/docs/en/slash-commands came back byte for byte the same as the skills page (same sha256), and that page says this about the old format:

  • “Custom commands have been merged into skills.”
  • “A file at .claude/commands/deploy.md and a skill at .claude/skills/deploy/SKILL.md both create /deploy and work the same way.”
  • “Your existing .claude/commands/ files keep working.”
  • “a Markdown file in .claude/commands/ is the older format and still works.”
  • “Prefer a skill for new work, since skills also support supporting files.”

If a skill and a command share a name, the name table on the same page answers “Which one runs”: “A skill and a file in .claude/commands/ | The skill”. A subfolder of commands/ becomes a prefix, as in “.claude/commands/frontend/component.md → /frontend:component”. A subfolder of agents/ does not: “The subdirectory path doesn’t affect how a subagent is identified or invoked, because identity comes only from the name frontmatter field.”

For skills vs subagents, the difference is where the work runs. The subagents page says “Each subagent runs in its own context window with a custom system prompt, specific tool access, and independent permissions”, and points you the other way for “reusable prompts or workflows that run in the main conversation context rather than isolated subagent context”. A skill can still hand its work to one: its context field is “Set to fork to run in a forked subagent context”, and its agent field picks “Which subagent type to use when context: fork is set.”

Make one of each and validate them

Everything below ran on this machine with claude --version printing 2.1.296 (Claude Code), in a throwaway directory: HOME and CLAUDE_CONFIG_DIR pointed at an empty scratch folder, CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC and DISABLE_TELEMETRY set, and no network at all. The validator prints absolute paths; the scratch HOME is shown as ~, and nothing else in the output is changed.

claude --version; echo "exit $?"
2.1.296 (Claude Code)
exit 0

The three files, one of each, in a project folder. The skill is cut down from the skills page’s first example, the command is adapted from its fix-issue example, and the subagent is its code-reviewer example without the model line:

mkdir -p .claude/skills/summarize-changes .claude/commands .claude/agents
cat > .claude/skills/summarize-changes/SKILL.md <<'EOF'
---
description: Summarizes uncommitted changes and flags anything risky. Use when the user asks what changed, wants a commit message, or asks to review their diff.
---

Summarize the output of git diff HEAD in two or three bullet points, then list any risks you notice.
EOF
cat > .claude/commands/fix-issue.md <<'EOF'
---
description: Fix a GitHub issue
argument-hint: [issue-number]
---

Fix GitHub issue $ARGUMENTS following our coding standards.
EOF
cat > .claude/agents/code-reviewer.md <<'EOF'
---
name: code-reviewer
description: Reviews code for quality and best practices
tools: Read, Glob, Grep
---

You are a code reviewer. When invoked, analyze the code and provide
specific, actionable feedback on quality, security, and best practices.
EOF
find .claude -type f | sort
.claude/agents/code-reviewer.md
.claude/commands/fix-issue.md
.claude/skills/summarize-changes/SKILL.md

claude plugin validate takes “a manifest file or a directory”. Given a directory named .claude, it checks “the skills, agents, and commands directories inside it”, and given “Any other directory: those three directories under its .claude”. This needs “Claude Code v2.1.233 or later” for a folder without a plugin manifest. A clean tree prints one line and exits 0:

claude plugin validate .claude; echo "exit $?"
Validating components in: ~/my-project/.claude

✔ Validation passed
exit 0

Six mistakes that hide a file, and which ones the validator sees

Each mistake below is planted in its own copy of that clean tree (t1 to t7), so each run shows one fault and nothing else; t7 is a seventh, YAML-only variant shown after mistake 6. Giving the validator the copy’s folder works because of the “Any other directory” rule above.

1. A skill with no frontmatter

Deleting the three frontmatter lines leaves only the instructions:

mkdir -p t1 && cp -r .claude t1/
sed -i '1,3d' t1/.claude/skills/summarize-changes/SKILL.md
claude plugin validate t1; echo "exit $?"
Validating components in: ~/my-project/t1

Validating skill: ~/my-project/t1/.claude/skills/summarize-changes/SKILL.md

⚠ Found 1 warning:

  ❯ frontmatter: No frontmatter block found. Add YAML frontmatter between --- delimiters at the top of the file to set description and other metadata.

✔ Validation passed with warnings
exit 0

It warns but passes. The skill still loads: per the skills page, description “If omitted, uses the first non-empty line of the markdown content.” A --- pushed down by a blank line counts as no frontmatter too, because “Claude Code reads the frontmatter only when the opening --- is the file’s first line.”

2. A command with no description

mkdir -p t2 && cp -r .claude t2/
sed -i '/^description:/d' t2/.claude/commands/fix-issue.md
claude plugin validate t2; echo "exit $?"
Validating components in: ~/my-project/t2

Validating command: ~/my-project/t2/.claude/commands/fix-issue.md

⚠ Found 1 warning:

  ❯ description: No description in frontmatter. A description helps users and Claude understand when to use this command.

✔ Validation passed with warnings
exit 0

Same verdict, worded for a command. For a skill or a command this is a style warning, not a broken file.

3. A subagent with no description

This is the one to fail CI on. The validator warns and exits 0, but the subagents page lists “A name but no description” among the files Claude Code skips, and “Claude Code skips the file and writes the reason to the debug log.” Only --strict turns it into a failure:

mkdir -p t3 && cp -r .claude t3/
sed -i '/^description:/d' t3/.claude/agents/code-reviewer.md
claude plugin validate t3; echo "exit $?"
claude plugin validate t3 --strict; echo "exit $?"
Validating components in: ~/my-project/t3

Validating agent: ~/my-project/t3/.claude/agents/code-reviewer.md

⚠ Found 1 warning:

  ❯ description: No description in frontmatter. A description helps users and Claude understand when to use this agent.

✔ Validation passed with warnings
exit 0
Validating components in: ~/my-project/t3

Validating agent: ~/my-project/t3/.claude/agents/code-reviewer.md

⚠ Found 1 warning:

  ❯ description: No description in frontmatter. A description helps users and Claude understand when to use this agent.

✘ Validation failed (--strict treats warnings as errors)
exit 1

4. A subagent with no name

mkdir -p t4 && cp -r .claude t4/
sed -i '/^name:/d' t4/.claude/agents/code-reviewer.md
claude plugin validate t4 --strict; echo "exit $?"
Validating components in: ~/my-project/t4

✔ Validation passed
exit 0

The validator reports nothing, even under --strict, and the docs say so too: it “doesn’t flag a file whose frontmatter parses but has no name.” In a session, a file with no name is read as documentation, so this subagent never appears, and no validator run will tell you. Check that every file in .claude/agents/ has a name line yourself.

5. A skill file in the wrong place

.claude/skills/summarize-changes.md instead of .claude/skills/summarize-changes/SKILL.md:

mkdir -p t5 && cp -r .claude t5/
mv t5/.claude/skills/summarize-changes/SKILL.md t5/.claude/skills/summarize-changes.md
rmdir t5/.claude/skills/summarize-changes
claude plugin validate t5 --strict; echo "exit $?"
Validating components in: ~/my-project/t5

✔ Validation passed
exit 0

It does not catch this one. The loose file is not even listed as checked, and the run passes under --strict. Every location on the skills page is a folder per skill, “.claude/skills/<skill-name>/SKILL.md”, so a skill that will not show up in the / menu should be checked against that shape first.

6. Frontmatter YAML that does not parse

An opening " on the description with no closing one:

mkdir -p t6 && cp -r .claude t6/
sed -i 's/^description: /description: "/' t6/.claude/skills/summarize-changes/SKILL.md
claude plugin validate t6; echo "exit $?"
Validating components in: ~/my-project/t6

Validating skill: ~/my-project/t6/.claude/skills/summarize-changes/SKILL.md

✘ Found 1 error:

  ❯ frontmatter: YAML frontmatter failed to parse: YAML Parse error: Unexpected EOF. At runtime this skill loads with empty metadata (all frontmatter fields silently dropped).

✘ Validation failed
exit 1

This is the only one of the six that fails without --strict, and its message matches the skills page: “If the frontmatter YAML is malformed, Claude Code loads the skill body with empty metadata, so /skill-name still works but Claude can’t match against your description.” For a subagent it is worse: the subagents page lists “YAML that doesn’t parse” among the files it skips.

The validator’s YAML reading is forgiving, though. An unclosed [ at the start of a subagent’s description is not valid YAML either, and it passed, even under --strict:

mkdir -p t7 && cp -r .claude t7/
sed -i 's/^description: /description: [/' t7/.claude/agents/code-reviewer.md
head -n 4 t7/.claude/agents/code-reviewer.md
claude plugin validate t7 --strict; echo "exit $?"
---
name: code-reviewer
description: [Reviews code for quality and best practices
tools: Read, Glob, Grep
Validating components in: ~/my-project/t7

✔ Validation passed
exit 0

So a clean run does not prove the frontmatter is valid YAML; it proves the validator could read it.

Pointing it at the wrong thing

The validator does not take a single skill. Given the skill’s folder or its SKILL.md, it looks for a plugin manifest and fails with a message that says nothing about skills:

claude plugin validate .claude/skills/summarize-changes; echo "exit $?"
claude plugin validate .claude/skills/summarize-changes/SKILL.md; echo "exit $?"
Validating plugin manifest: ~/my-project/.claude/skills/summarize-changes

✘ Found 1 error:

  ❯ directory: No manifest found in directory. Expected .claude-plugin/marketplace.json or .claude-plugin/plugin.json

✘ Validation failed
exit 1
Validating plugin manifest: ~/my-project/.claude/skills/summarize-changes/SKILL.md

✘ Found 1 error:

  ❯ json: Invalid JSON syntax: JSON Parse error: Invalid number

✘ Validation failed
exit 1

The path must be the .claude folder, a folder that holds one, or a folder named skills, agents or commands; the skills page gives claude plugin validate .claude/skills and claude plugin validate ~/.claude/skills as its examples.

In CI: strict mode, JSON output and the exit code

The plugin commands reference gives the exit codes: “0 on success and 1 on failure. validate adds exit 2 for an unexpected error”. --strict will “Treat warnings as errors, so unrecognized fields and missing metadata that the runtime tolerates fail the run”, and --json prints “the validation report as one JSON object with the same exit codes”, which “Requires Claude Code v2.1.259 or later”. On the clean tree:

claude plugin validate .claude --strict --json; echo "exit $?"
{
  "success": true,
  "strict": true,
  "target": "~/my-project/.claude",
  "manifest": null,
  "contents": [],
  "advice": []
}
exit 0

On the copy with the subagent that has no description:

claude plugin validate t3 --strict --json; echo "exit $?"
{
  "success": false,
  "strict": true,
  "target": "~/my-project/t3",
  "manifest": null,
  "contents": [
    {
      "file": "~/my-project/t3/.claude/agents/code-reviewer.md",
      "type": "agent",
      "errors": [],
      "warnings": [
        {
          "path": "description",
          "message": "No description in frontmatter. A description helps users and Claude understand when to use this agent.",
          "code": null
        }
      ],
      "notes": [],
      "gatingHooks": []
    }
  ],
  "advice": []
}
exit 1

The fields are the ones the reference lists: success, strict, target, manifest (“null for a run without a manifest”) and contents, whose entries each name a file and carry errors, warnings and notes. A CI step needs nothing more than claude plugin validate .claude --strict, since the exit code already carries the verdict; the JSON is for a job that wants to print the failing files. Exit 2 did not happen in any run here.

Arguments in a skill or command

The fix-issue command above uses $ARGUMENTS. The skills page defines the placeholders:

  • $ARGUMENTS: “All arguments passed when invoking the skill.”
  • $ARGUMENTS[N]: “Access a specific argument by 0-based index, such as $ARGUMENTS[0] for the first argument.”
  • $N: “Shorthand for $ARGUMENTS[N], such as $0 for the first argument or $1 for the second.”

Its worked example: “When you run /fix-issue 123, Claude receives “Fix GitHub issue 123 following our coding standards…"" With positions, “Running /migrate-component SearchBar JavaScript TypeScript replaces $ARGUMENTS[0] with SearchBar, $ARGUMENTS[1] with JavaScript, and $ARGUMENTS[2] with TypeScript.” If the file has no placeholder at all, “Claude Code appends ARGUMENTS: <your input> to the end of the skill content so Claude still sees what you typed.” The argument-hint field only shows a hint while you type, “Example: [issue-number] or [filename] [format]”.

Not run here: typing the command needs a Claude Code session, and none was started for this page.

/fix-issue 123

So the substitution above is the docs’ description, not something observed on this machine. The validator does not look at placeholders at all: the fix-issue file passed with $ARGUMENTS in it.

Settings files are a different thing

.claude/ also holds settings.json, which is not a skill, command or subagent and is not checked by this validator. Where those files live and which one wins is in the settings.json post.

What ran here and what did not

Ran: claude --version and every claude plugin validate shown above, in a scratch HOME with no network; the outputs above are what came out, with only the scratch path shortened to ~. Not run: any Claude Code session. No skill, command or subagent on this page was loaded or invoked by Claude Code, so what a session does with each file (skipping it, loading it with empty metadata, substituting arguments) is quoted from the docs, not observed.

← Back to all posts

Follow new posts by RSS: paste dankdev.com/feed.xml into any feed reader. It is a plain XML file — no signup, no email address, no account. Posts land there the same day they go up, and there is nothing to unsubscribe from later.

Back to the top