If you have been using Claude Code for a while you have definitely run into three terms: skill, subagent and hook. All three live under “customizing Claude Code” and at first glance they look like they do the same job.

They don’t. Each solves a different problem, and picking the wrong one means either needless complexity or a setup that never fires 🛠️

The Short Answer

The decision rule
  • Skill: when you keep writing the same instructions over and over
  • Subagent: when a job produces so much output it floods your main conversation
  • Hook: when you need a rule to hold without exception

Slash Commands Are Skills Now

The biggest change first, because most older guides miss it: custom slash commands have been merged into skills.

A file at .claude/commands/deploy.md and a folder at .claude/skills/deploy/SKILL.md do the same thing, both create /deploy. Your old files keep working, nothing broke.

What skills add on top:

  • A folder for supporting files (templates, scripts, reference material)
  • Control over who can invoke the command
  • The ability for Claude to load it on its own when relevant

If both share a name, the skill wins.

What Is a Skill and How Do You Write One?

A skill is a folder containing a SKILL.md file. You write instructions, Claude adds it to its toolkit.

When should you write one? When you keep pasting the same instructions, checklist or multistep procedure into chat. Or when a section of your CLAUDE.md has stopped being a fact and become a procedure.

Why not just put it in CLAUDE.md?

CLAUDE.md loads into context in full, on every session. A skill’s body loads only when it is used.

So a long reference document as a skill costs you almost nothing until you need it. The same text in CLAUDE.md costs you on every single launch.

Where Does the File Go?

LocationPathWho gets it
Personal~/.claude/skills/<name>/SKILL.mdAll your projects
Project.claude/skills/<name>/SKILL.mdThat project, shared with your team

The command name comes from the folder name. A file at .claude/skills/deploy-staging/SKILL.md creates /deploy-staging.

Check project skills into version control so your team can use and improve them.

An Example Skill

---
name: deploy
description: Deploy the application to production
disable-model-invocation: true
---

Deploy $ARGUMENTS to production:

1. Run the test suite
2. Build the application
3. Push to the deployment target
4. Verify the deployment succeeded

The fields worth knowing:

FieldWhat it does
descriptionWhat the skill does. Claude decides when to use it from this, the most important field
disable-model-invocationtrue stops Claude running it on its own, only you can invoke
user-invocablefalse hides it from the menu, only Claude uses it
allowed-toolsTools usable without a permission prompt while the skill runs
modelModel to use while the skill is active
pathsOnly activate the skill when working with matching files
Write the description well

Claude decides when to use a skill purely from the description field. Write it vaguely and it either never triggers or triggers in the wrong places.

Put the main use case first, and skip long lists.

Passing Arguments

The $ARGUMENTS placeholder is replaced with whatever follows the skill name:

---
name: fix-issue
description: Fix a GitHub issue
disable-model-invocation: true
---

Fix GitHub issue $ARGUMENTS following our coding standards.

Running /fix-issue 123 gives Claude “Fix GitHub issue 123…”.

Stop Claude Running It on Its Own

By default both you and Claude can invoke any skill. That is not always what you want.

Setting disable-model-invocation: true means only you can invoke it. Essential for anything with side effects: /deploy, /commit, /send-slack-message. You do not want Claude deciding to deploy because your code looks ready.

The reverse exists too: user-invocable: false hides the skill from the menu so only Claude uses it. That makes sense for background knowledge, like a note explaining how a legacy system works, since invoking it as a command would be meaningless.

What Is a Subagent and When Do You Use One?

A subagent is a separate helper running in its own context window, with its own system prompt and its own tool permissions.

What is it for? When a side task would fill your main conversation with search results, logs or file contents you will never look at again, the subagent does that work in its own context and returns only the summary.

What you get:

  • Preserved context: the noise stays out of your main conversation
  • Enforced limits: you decide which tools it can use
  • Lower cost: route the job to a fast, cheap model like Haiku

An Example Subagent

The file goes in .claude/agents/:

---
name: code-reviewer
description: Reviews code for quality and best practices
tools: Read, Glob, Grep
model: sonnet
---

You are a code reviewer. When invoked, analyze the code and give
concrete feedback on quality, security and best practices.

The frontmatter is configuration, the body becomes the system prompt.

A subagent cannot see your conversation

A subagent gets only its own system prompt plus basic details like the working directory. It sees nothing of what you discussed in the main conversation.

So the brief you give it has to stand alone. “Review the file I mentioned earlier” will not work.

Subagent or Main Conversation?

Stay in the main conversation when:

  • The work needs frequent back-and-forth or iterative refinement
  • Planning, implementation and testing share the same context
  • You are making a quick, small change

Open a subagent when:

  • The job produces long output you will never revisit
  • You want to restrict tool access
  • The work is self-contained and can return a summary

What Is a Hook and What Is It For?

Hooks are scripts that run when something happens in Claude Code. The distinction that matters: prompts depend on the model’s interpretation, hooks run code and cannot hallucinate.

If you need a rule to hold without exception, do not write it in a prompt, write a hook.

The events you will use most:

EventWhen it runsCan it block?
PreToolUseBefore a tool runsYes
PostToolUseAfter a tool has runNo, it already happened
UserPromptSubmitWhen you send a messageYes
SessionStartWhen a session beginsNo
StopWhen Claude finishes respondingYes, it can keep it going

Example: Blocking a Destructive Command

Hooks are defined in .claude/settings.json:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/block-rm.sh"
          }
        ]
      }
    ]
  }
}

And the script itself:

#!/bin/bash
COMMAND=$(jq -r '.tool_input.command')

if echo "$COMMAND" | grep -q 'rm -rf'; then
  echo "Destructive command blocked" >&2
  exit 2
fi

exit 0

The rule is simple: exit code 2 means block. Whatever you write to stderr goes back to Claude as an error message. Exit code 0 and everything carries on as normal.

Use Pre if you want to block

You cannot block anything with PostToolUse, because the tool has already run. There you can only show a warning.

Anything you want to stop needs PreToolUse.

Which One When?

What do you need?I keep rewriting thesame procedureOutput floods themain conversationA rule must holdwithout exceptionSkillSubagentHookinvoked with /nameown context, returns a summarydeterministic code, can blocklowers context costrestricts tool accessnot left to model judgment What do you need?I keep rewriting thesame procedureOutput floods themain conversationA rule must holdwithout exceptionSkillSubagentHookinvoked with /nameown context, returns a summarydeterministic code, can block
The rough decision tree. Detailed mappings are in the table below.
What you needUseWhy
I keep pasting the same promptSkillMove it to a file, invoke with /name
The procedure ships with templates or scriptsSkillEverything lives in one folder
Long reference material I rarely needSkillLoads only when invoked
The job floods my conversation with outputSubagentOwn context, returns a summary
I want to route work to a cheaper modelSubagentmodel: haiku cuts cost
A rule must hold without exceptionHookCode runs, not model judgment
Tests must run before every commitHookPreToolUse can block
Claude should not do this on its ownSkill + disable-model-invocationOnly you can invoke it

Common Mistakes

1. Dumping everything into CLAUDE.md. It loads in full every session. Once a section turns into a procedure, move it to a skill.

2. Leaving side-effecting commands unguarded. Without disable-model-invocation: true, Claude may run your /deploy skill whenever it judges the moment right.

3. Assuming the subagent knows the conversation. It does not. Write a complete brief.

4. Trying to block with PostToolUse. The tool already ran. You need PreToolUse.

5. Opening a subagent for everything. Work that needs back-and-forth finishes faster in the main conversation, since a subagent starts from zero.

Frequently Asked Questions

What is a Claude Code skill? A skill is a folder containing a SKILL.md file. You write your instructions inside and Claude adds it to its toolkit. Unlike CLAUDE.md content, a skill’s body loads into context only when used, which makes long reference material almost free.

Were Claude Code slash commands removed? They were not removed, they were merged into skills. A file at .claude/commands/deploy.md and a folder at .claude/skills/deploy/SKILL.md both create /deploy. Your old files keep working, and if both share a name the skill takes precedence.

What is the difference between a skill and a subagent? A skill runs in the main conversation’s context and adds instructions there. A subagent runs in its own context window with its own system prompt and tool permissions, returning only a summary when done. Use a subagent for jobs with long output and a skill for repeated procedures.

What is a Claude Code hook? A hook is a script that runs when a specific event happens in Claude Code. Unlike prompts it runs code and cannot hallucinate, which makes it the right tool for rules that must hold without exception. Hooks are defined in .claude/settings.json.

How does a hook block a command? The script returning exit code 2 means block, and whatever it writes to stderr goes back to Claude as an error message. Blocking only works on events that run before the action, like PreToolUse. PostToolUse cannot block because the tool has already run.

How do I stop Claude from running a skill on its own? Add disable-model-invocation: true to the frontmatter of your SKILL.md. Only you can then invoke the skill by command. It is recommended for anything with side effects, such as deploying, committing or sending messages.

Where do Claude Code skill files go? Personal skills go in ~/.claude/skills/<name>/SKILL.md and project skills in .claude/skills/<name>/SKILL.md. The command name comes from the folder name, so a deploy-staging folder creates /deploy-staging.

Can a subagent see the main conversation? No. A subagent receives only its own system prompt and basic details like the working directory, and sees nothing of what was discussed in the main conversation. The brief you give it has to make sense on its own.

Summary

The three are not alternatives, they complement each other:

  • Skills move repeated procedures into files and cut context cost
  • Subagents take noisy work out of the main conversation and restrict tool access
  • Hooks enforce rules in code rather than leaving them to the model

The most common working combination: make the procedure a skill, push the heavy exploration into a subagent, and guard the irreversible steps with a hook.

For the thinking behavior of the models underneath Claude Code see the extended thinking versus adaptive thinking post, and for adding tools see the MCP guide 👀