AIDive

Stop Retyping Claude Code Prompts: 4 Jobs Become 4 Files

By AIDive · Published

Coding agentsAutomation & workflows

Four prompts you keep retyping become four files

Claude Code extensions are the files a repo can hold so you stop retyping the same prompt: a skill, a hook, a subagent and an MCP server. We ran four recurring jobs on one real repo, a small invoicing API with a JSON file as its database. Each job was first given the wrong extension, and the wrong pick failed on screen every time. Then each became a committed file. This page covers the four gestures and the picking card that decides which kind to use.

Two results frame everything below. A hook is often described as a guarantee and a memory file rule as a request, yet our first hook ran, printed its warning, and the write went through anyway. What turns a hook that runs into a hook that blocks is covered in the hook section. The first docs server we plugged in changed nothing, and the one that answered was the tool's own docs server. The hook recipe most people copy also needs a separate script file, but ours fits entirely in the settings file.

Before writing any of the four files, you need to know what your session already loads.

What the four are, and what your session already loads

A Claude Code extension is a file you add to your repo so you stop retyping the same instructions into the prompt box. Four kinds exist, and readers who already know them can skip to the next section.

  • A skill is a runbook filed in your repo. Claude loads its steps only when that job comes up, and you can start it by typing its name.
  • A hook is like a commit hook in git, but for Claude's own actions. It is a script that runs on an event, such as an edit, whatever Claude intended.
  • A subagent is a contractor with a blank desk. It gets your brief, does the reading, and sends back a report instead of the pile.
  • An MCP server is an API client Claude can call, for data that is not on your disk, such as a database or the docs of a tool.

To measure anything, you first need a baseline. Open a fresh session and run the context command. It prints a table of everything loaded before your first word. On our bench, the total was 26.2k tokens out of a 1m window, with no memory files line and no MCP line at all. Every job you give Claude from now on either adds a line to that table or leaves it alone.

The first job: the release checklist becomes a skill

A skill is a checklist filed in its own folder that Claude reads only when you call it. The first job is a release checklist you keep pasting into the prompt box: typecheck, tests, bump the version, add a changelog line, commit, and tag.

The obvious place for it is the memory file, and that is the wrong pick, because everything in that file loads on every turn of every session. We put the steps there and read the context table again. It had grown a Memory files line of 210 tokens, loaded whether or not you are releasing. The docs say as much: when a section of that file has grown into a procedure, it belongs in a skill.

The right pick is a folder under the skills directory with the steps in .claude/skills/release/SKILL.md, a short description at the top, and one extra frontmatter line, disable-model-invocation: true. That line keeps the skill off Claude's own list, so only you can start it, which is what you want for something that commits and tags. You then type a slash and its name, and the checklist runs.

Context reading Total Memory files line
Baseline, fresh session 26.2k / 1m none
Checklist in the memory file 26.4k / 1m 210 tokens
Checklist as a skill 26.2k / 1m none, skill not listed

When we ran it, Claude completed all six steps in under a minute: typecheck, 34 tests, version bump from 0.4.0 to 0.4.1, changelog line, commit and tag. It also caught a flaw in our own checklist. The commit command as written skips untracked files, so Claude staged the new changelog first and said so. The release is now a command you type, it lives in the repo for the whole team, and it costs nothing in context until you type it.

The second job: the rule that must hold becomes a hook

A hook is a script Claude Code runs before a tool fires, here before any edit or write, and what that script answers decides what happens next. The second job is a rule that must never break: in this repo the stored data file is never edited by hand, and every change goes through a migration.

Writing that rule in capitals in the memory file does not enforce it. The docs are direct: that file is context, not enforced configuration, and an instruction like "never edit this file" is a request, not a guarantee, while a hook that blocks the edit is enforcement.

The obvious first hook is the one we wrote. In the settings file it watches for writes to the data file, prints a warning, and exits with code zero, like a logging script. We then asked Claude to change an invoice amount directly in the file, with no migration. The hook ran and the warning printed, but the write went through: the unit price on the third invoice went from 8000 to 12000. Claude's reply never mentioned the hook, because a hook that exits clean and prints nothing has made no decision, and its warnings never reach Claude.

The guarantee is not the hook running, it is the answer it returns. The fix keeps the same hook and makes it return a deny decision: a short JSON line carrying the reason you want Claude to read. Exiting with code two blocks the same way. The whole hook is one inline command in .claude/settings.json, with no script file:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Edit|Write",
        "hooks": [
          {
            "type": "command",
            "command": "jq -c 'if (.tool_input.file_path // \"\") | test(\"data/.*\\\\.json$\") then {hookSpecificOutput: {hookEventName: \"PreToolUse\", permissionDecision: \"deny\", permissionDecisionReason: \"Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions).\"}} else empty end'"
          }
        ]
      }
    ]
  }
}

With the same request, the edit was refused and the data file stayed untouched. Claude saw this error: PreToolUse:Edit hook error: Stored data is never edited by hand: write a migration in src/store/migrations.ts (README § Conventions). It said it had changed nothing and asked whether to override the guardrail or write the migration.

The limit, which Claude named itself: this hook watches the edit and write tools only, and in a later run Claude offered to make the change through the shell instead. A hook has to cover the shell too, or the shell has to stay off your allow list, because a guard that covers only one door is still a request. The hook costs nothing in context, because it runs outside your conversation.

The third job: the audit that floods becomes a subagent

A subagent is a separate Claude session with its own blank context. It receives your brief, reads what it needs, and only a summary comes back. The third job is an audit that floods your thread: check every file under the source folder against the README conventions, and list every violation with file and line.

Asked inline, Claude reads every file into your conversation, and all of that content stays there for the rest of the session. In our run Claude read all 19 source files, the Messages line of the context table ballooned, and the findings were buried under the pile.

The right pick is a file under .claude/agents/, here .claude/agents/conventions-auditor.md, with a name, a description of when to use it, the tools it may use, and a pinned model. Pinning the model matters, because a subagent otherwise inherits whatever your session runs on, and most reading jobs do not need the big one.

We asked the exact same question again, and Claude read the files itself. In both attempts it never called the agent, even though it was listed: 0 of 2 delegations. What worked was naming it at the start of the prompt: "use the conventions auditor subagent". A single call went out and a short summary came back.

Run Total context Messages line
Inline audit 46.1k 19.3k
Named subagent 34.1k 7.2k

The findings were as good: the subagent caught a path bug the inline run had missed, and missed one the inline run found. So the file makes the subagent exist, and your prompt makes Claude use it; until Claude picks it on its own, you name it. One limit: the bill did not move. Both runs cost the same, because the reading still happens, just not in your window.

The fourth job: the data Claude cannot reach becomes an MCP server

An MCP server is an API client Claude can call, and the job it exists for is data newer than the model or data that lives off your disk. The reflex pick here is wrong, so we made it first.

We plugged in a public docs server for our test library and asked Claude for a test with fake timers. Nothing changed: Claude already knew that library, and called the server only when we named it. Docs the model already knows are not data it cannot reach.

The real test is a question about Claude Code itself, about a hook setting newer than the model: which setting makes a failed hook block, which version added it, and what the exact JSON is. Without a server, Claude read the settings schema of its own install, found nothing, and answered that it could not find the setting and would not guess one. That answer is honest and useless, because the setting exists; it arrived after the model's knowledge.

The fix is to add Claude Code's own docs server to the repo, with project scope so the config lands in a file at the repo root that your whole team gets:

claude mcp add --transport http claude-docs --scope project https://code.claude.com/docs/mcp

This writes .mcp.json. Claude Code lists the server as pending until you approve it in your next session, and from then on its tools are available to Claude. We asked the same question in plain words, without naming the server. Claude searched the docs, found the page, and answered with the setting, the version and the JSON to add, with the link.

The setting is "onFailure": "block", which makes a failing hook block the action instead of letting it through. It arrived in v2.1.295, while our install was still 2.1.294. That is the limit: Claude never said our install could not use it, because it did not check the version, so you check your version yourself. In the context table the server shows up as its tool names only, loaded on demand until Claude reaches for one.

Four files, one picking card

After the four jobs, git status shows exactly four files, because the hook needs no script of its own:

  • .claude/skills/release/SKILL.md
  • .claude/settings.json
  • .claude/agents/conventions-auditor.md
  • .mcp.json

Commit them, and the next session on this repo has all of them, as does anyone who clones it.

The reason each pick was right is a table the docs ship, which you can keep as a decision card:

Situation Extension
A procedure you paste yet again Skill
Something that must happen every time, without asking Hook
A side task that floods your conversation with output you will not read again Subagent
Data you keep copying from somewhere Claude cannot see MCP server

The kinds also combine. The docs pair a skill with an MCP server: the server provides the connection, and the skill teaches Claude how to use it well. A skill can also run inside a forked subagent, so its reading stays out of your window. When another repo needs the same setup, a plugin is the box that ships all of them together.

The card decides the kind, not the quality. A hook that only logs, a subagent you never name, and a server for docs the model already knows each sat in the right kind and still failed. Pick the kind from the card, then make the file answer, not just exist.

Verdict: when each one, when none, the one rule

All four jobs you used to retype were tried the wrong way first, failed on screen, and are now files in the repo that work. The release is a command you type. The data rule is a hook that refuses the edit and makes Claude ask. The audit comes back as a summary. The docs question gets the setting and the version Claude could not find alone.

The hook we opened on, the one that ran and let the write through, taught the rule under all of this: a hook decides only through what its command returns, so a hook that only logs is a request in a costume.

Some cases call for none of these:

  • A convention Claude keeps getting wrong still belongs in the memory file, as a line, not as a procedure.
  • A job you need to steer back and forth, with your history in view, stays in the main thread and not in a subagent.
  • A docs server for a library the model already knows costs context and changes nothing. Connect what the model cannot have, not what it already has.

The rule to remember: give every job you retype its own file, pick the kind from the card, and know that among them, only the hook never asks.

Free resources

Sources

Frequently asked questions

Should I use a Claude Code skill, hook, subagent or MCP server?
Use a skill for a procedure you keep pasting, a hook for something that must happen every time without asking, a subagent for a side task that floods your conversation, and an MCP server for data Claude cannot see. The docs ship this as a decision table.
Why did my Claude Code hook run but not block the edit?
A hook decides only through what its command returns. A hook that prints a warning and exits with code 0 has made no decision, so the edit goes through; returning a permissionDecision of deny, or exiting with code 2, blocks it.
Should I put a release checklist in CLAUDE.md or in a skill?
In a skill. Everything in the memory file loads on every turn of every session (210 tokens for our six-step checklist), while a skill with disable-model-invocation set costs nothing in context until you type its name.
Why does Claude Code not call my subagent automatically?
Even with a description that matched our prompt almost word for word, Claude read the files itself in 2 of 2 runs. Naming the subagent at the start of the prompt made it delegate, and the Messages line dropped from 19.3k to 7.2k tokens.
When is an MCP server worth adding to Claude Code?
When the data is newer than the model or lives off your disk. A docs server for a library the model already knows changed nothing, while the Claude Code docs server answered a question about a hook setting that the install's own schema did not contain.
How do I add the Claude Code docs MCP server to a repo?
Run claude mcp add --transport http claude-docs --scope project https://code.claude.com/docs/mcp. It writes .mcp.json at the repo root, and the server shows as pending until you approve it in your next session.

Related videos