Moving the Instruction Out of the Prompt
Turning repeated instructions into repository rules
The chapter walks through five places to put an instruction so it stops living in the same three sentences typed at the top of every session. A hook is a shell command wired to a fixed point in the loop — format the changed file and run the typecheck after every write, no memory required — and the Claude Code hooks documentation covers how they can also block an action outright, which is distinguished from a flat permissions deny rule by the fact that a hook can inspect state before refusing. A custom slash command turns a retyped personal habit, like a pre-commit diff review, into a named file the whole team shares. A skill goes further: a folder whose short description is always visible but whose full body — doctrine plus a real example file — only loads when the work actually matches, exactly as laid out in the Claude Code skills documentation. A one-off subagent handles the heavy-input, light-output question — search forty files, report three paragraphs — per the Claude Code subagents documentation. And an MCP server connects to a system outside the repo entirely — an issue tracker, a read-only database — as described in the guidance on connecting Model Context Protocol servers, best added only for a system you keep pasting from. The chapter closes on the risk of over-wiring — too many hooks, skills and servers making a failure's cause unreadable — and the fix of stripping back to a bare session and re-adding one layer at a time.
This week's releases
A short rundown of recent changes: a new diagnostic command flags which skills are eating context, new models default to a million-token window with a much cheaper cache-read price, and a supply-chain fix stops a cloned repo from silently redirecting a client's configuration.
Open the same project we have been working in all along — the TypeScript app on Next.js, Postgres behind it, tests and typecheck wired to the usual scripts — and start a session the way you did yesterday. Watch what you type before you type anything about the actual task.
If you are like most people three weeks into working this way, the first thing you type is not the task. It is three sentences you have typed before. Run the formatter after you edit a file, and run the typecheck, because you have been burned twice by a session that left the repo not compiling. Do not touch the migrations folder without asking me first, because a session once helpfully renamed a column in a migration that had already run in production. And here is how we write a route handler in this project: the validation goes here, errors come back in this shape, the database call goes through this helper and not the client directly.
Then, and only then, the actual task. Add a filter to the orders list.
Count the words. Three quarters of what you typed was not about orders. It was about the project. And you typed it yesterday, and you will type it tomorrow, and each time you type it slightly differently, which is worse than typing it the same way, because the session gets a slightly different set of rules every day and you have no idea which version produced which result.
That is the thing to fix today, and it is worth saying the fix in one line before we do any of it. Anything you find yourself typing into a prompt more than twice is not a prompt problem. It is a repo problem. The instruction should live in the repository, in a file, under version control, where it applies whether or not you remember to say it and where a teammate can read it in review.
Now, you already did some of this. Back in the starter episode we generated the project memory file and then cut it down to what was actually true — the real test command, the real layout, the two or three conventions somebody would genuinely maintain. That file is doing real work, and it is already carrying the third of my three sentences reasonably well. What it cannot do is the other two. The memory file is read; it is not enforced. It tells the model what you would like. It does not make the formatter run, and it does not stop a write to the migrations folder, and if you keep stuffing more and more into it in the hope that volume becomes enforcement, you will end up with a two-thousand-word file that is loaded into every single session, including the ones where you are debugging a Postgres index and could not care less about route handler conventions.
So today we take the rest of that load off it, and we spread it across five places. And the only real skill here — the thing worth carrying out of this episode — is knowing which of the five a given repeated instruction wants. They sort cleanly, and they sort by one question: who decides that this instruction fires, and when.
Something that must happen every time, whether or not the model remembers, wants a hook. Something you want to invoke by name, when you decide, wants a custom slash command. Something that should be loaded only when the work is actually about that thing, and the judgment of relevance is the model's, wants a skill. Something that is a big boring read you do not want cluttering your working session wants a one-off subagent. And something that needs an answer from a system outside the repository entirely wants an MCP server. Five places. Same sorting question each time.
Start with the deterministic case, because it is the one that solves my first sentence and the one people underuse most.
A hook is a shell command that Claude Code runs at a fixed point in the loop. Not when it feels relevant. Not when the model decides. At the point. The one you want first fires after the session writes to a file, and what it runs is your formatter on the changed file, and then your typecheck. That is it. That is the whole thing, and it retires a sentence you have typed a hundred times.
Notice what changed and what did not. Before, the model was responsible for remembering to format. It usually did. Usually is the problem — usually means the failures are rare, and rare failures are the expensive kind, because you stop checking. After, the model's memory is not in the loop at all. The formatter runs because the write happened. If the model forgets, the formatter still runs. If the model is mid-way through a bad plan, the formatter still runs. You have converted a request into a property of the repository.
Two practical notes, because a hook that is annoying gets deleted and then you are back where you started. Keep it fast. This thing runs after every write, so a hook that takes eight seconds turns a ten-edit task into eighty seconds of waiting that you did not previously have. Format the file that changed, not the whole tree. And keep it quiet on success — a hook that prints nine lines of triumph every time will train you to ignore its output, which means you will also ignore the line where it says the typecheck failed.
Now the second thing hooks do, which is the part that connects today to last episode. A hook can block. It can look at what the session is about to do and refuse, with a message explaining why, and the session gets that message and has to deal with it.
That should immediately raise an objection, and it is the right objection: we already did this. We spent a whole episode on permissions — the allow and deny and ask lists, the three settings layers, deny beating everything. Why would I write a hook to block something when I can write a deny rule in one line?
Because they answer different questions, and the division of labor is worth memorizing. Permissions decide what may happen. Hooks decide what always happens.
A deny rule is a rule about a shape. It matches a command or a path pattern, and it says no, full stop, no override, and that is exactly what you want for anything irreversible — production credentials, a force push, history rewriting. It is a wall. A wall is a good thing to have and it needs no reasoning.
A hook is code. It runs, it can look at state, and it can decide. Which means it can express the rule my second sentence actually wanted, which was never a flat no. What I wanted was: you may not silently edit a file in the migrations folder that has already been applied, but the pending one you just generated is fine. That is not a shape. A deny rule cannot see which migrations have run. A hook can shell out, check, and refuse only the dangerous case — with a message that says which file and why, so the session stops instead of thrashing against a wall it does not understand.
So the rule of thumb. If you can express it as a pattern and the answer is always no, use a deny rule; it is one line and it never breaks. The moment you find yourself wanting the answer to be "no, unless," you have crossed into hook territory. And if you find yourself writing a hook whose whole body is a flat refusal with no logic in it, go back and make it a deny rule instead, because you have just written twenty lines to do what one line does more reliably.
That is my first two sentences retired. The third one — how we write a route handler here — is harder, and to get to it properly we go through something simpler first.
There is a category of thing I have not mentioned yet, which is the prompt you retype not because the project needs it but because you need it. Mine is the pre-commit read. Before I commit, I want the diff read back to me: what changed, what it touches that I might not have noticed, anything in it that looks unintentional. I have typed that paragraph in various forms for months. It is four or five sentences and I get it slightly wrong every time.
That is a custom slash command, and it is the cheapest primitive in this whole episode. A custom slash command is a file containing a prompt. The file lives in the project's Claude directory, in the commands folder, and the file's name becomes the command's name. Put a file called review-diff in there, and you type slash review-diff, and the contents of that file get sent as your prompt.
That is genuinely all it is. The prompt you kept retyping, saved, with a name.
Two things make it more than a text snippet. First, arguments — the command can take what you type after the name and drop it into the prompt, so slash review-diff followed by a branch name reviews against that branch instead of the default. Second, and this is the part people miss, it is a file in the repository, so it goes through code review like anything else. When someone on the team improves the review prompt, everyone's slash review-diff gets better. You have turned a personal habit into shared tooling, and it cost you one file.
The line between this and a hook is exactly the who-decides line. My formatter runs whether I want it to or not; that is the point of it. My diff review runs when I say so, because reviewing the diff halfway through a task is noise. If you catch yourself building a hook that fires on every write and then find yourself irritated by it firing, the honest answer is usually that you wanted a command.
Which brings us to the third sentence, the route handler conventions, and to the primitive that needs the most unpacking.
Look at why that one resists the tools we have so far. It is too long for a slash command — well, no, it would fit, but you would have to remember to invoke it, and the whole problem is that at the moment you are writing a route handler you are thinking about the feature, not about invoking a guidance command. So it wants to be automatic. But it cannot be a hook, because it is not a shell command, it is guidance. And you already know why it should not just live in the memory file: because it is four hundred words of route handler doctrine that is dead weight in every session that is not about route handlers, and if you add the equivalent for database work and for the test suite and for the styling system, you have built a preamble the size of a small chapter that gets loaded before every single prompt.
A skill is the answer to exactly that. It is a folder in the project's Claude directory holding a set of instructions and, if you want, helper files alongside them — a snippet to imitate, a checklist, a script. At the top of the instruction file is a short description of what this skill is for and when it applies. And here is the mechanism: only that short description is in front of the model most of the time. The body is not loaded. When the work at hand actually looks like the thing the description names, the skill gets pulled in, and now the model has the full four hundred words and the example file.
Read that mechanism twice, because it is doing something subtle. You are not paying for the guidance until you need it. Your route handler doctrine costs you one sentence of context on a database chore and costs you the whole thing on a route handler, which is precisely the bargain you wanted and could not get from a memory file.
Now the question that actually distinguishes a skill from a slash command, since both are a folder of prose that produces guidance. It is not size, and it is not structure. It is who decides it applies. With a slash command, you decide, by typing its name. With a skill, the model decides, by reading the description and judging that this is the situation the description is talking about.
That has a consequence you should feel before you write your first one. If the model decides, then the description is not documentation. The description is the trigger. A skill whose description says something vague about backend best practices will either never fire or fire constantly, and both failures look like the skill being broken when the actual fault is one sentence at the top. Write it as the condition, concretely and in the project's own vocabulary: for adding or modifying a route handler under the app directory, including its validation and error shape. Name the folder, name the artifact, name the words you would actually use. Then it fires when you are doing that and stays out of the way when you are not.
Second thing to feel. Because it is a folder, the body can be more than prose. My route handler skill has the doctrine, and next to it a single small file that is a good route handler — real, from this repo, one that we actually like. That file is worth more than the doctrine, because a pattern to imitate is unambiguous in a way that a paragraph of rules never is. If you write only one skill after this episode, write the one where you already have a canonical example sitting in the codebase, and let the skill be mostly a pointer to it.
And an honest limit: a skill is guidance, not enforcement. The model can read it and still get it wrong. If a convention matters enough that a violation must never survive, the convention needs a lint rule, and a hook that runs the linter. The skill and the hook are not rivals. The skill teaches the shape so the model gets it right the first time; the hook catches the day it does not.
Four sentences, four homes — and one more thing that has been quietly eating your sessions, which is not an instruction at all.
You know the request. Where do we handle authentication? Not in one file — the honest answer is scattered across some middleware, a couple of helpers, a config file and about thirty route handlers. So the session goes and reads them. All of them. And it comes back with a good three-paragraph answer, and your context window is now mostly the contents of forty files you will never think about again, and the actual feature work you were doing before that question is buried under it.
That is what a one-off subagent is for. You hand off a bounded, read-heavy job to a fresh context. It goes and does the reading in its own window, with its own budget, and reports back one answer. What comes into your session is the three paragraphs. The forty files never arrive.
I want to be careful about how this is framed, because it is easy to hear more here than is being offered. This is delegation, and that is all it is today. One job, sent out, one answer back. You are still the one deciding to send it and still the one reading what returns.
Where it earns its keep is a specific shape of task, and the shape is worth naming so you can spot it: heavy input, light output. Search the codebase for every place we construct a database client directly instead of going through the helper, and list the files. Read the last twenty commits touching the checkout flow and tell me what changed in the error handling. Both of those consume enormously and produce a paragraph. Perfect.
And where it does not work, which you should hear from me now rather than discover: anything where you want to keep talking. If the answer to the question is going to be the first move in a conversation — you look at the code, you have a hunch, you want to poke at it — do not delegate it, because the subagent's context evaporates when it reports back. All the reading it did, all the detail it noticed and did not mention, gone. You get the summary and nothing else. Sending out something you wanted to discuss is a way of throwing away the good part.
The last of the five reaches somewhere else entirely.
Everything so far has been about the repository. But a real chunk of what you type into a session is not about the repository at all. It is you, being a human clipboard. You paste in an error from your logs. You paste in the issue text from the tracker. You describe the shape of a table because the session cannot see the database.
An MCP server is a connection to a system the session otherwise cannot see. You configure it with a command to run, the session starts it, and it exposes some capabilities the model can call. Once your issue tracker is connected, the session can go read the issue instead of waiting for you to paste it. Once a database connection is available in read-only form, the session can look up the actual schema instead of trusting your description of it, which — be honest — has been wrong at least once.
The discipline here is one sentence, and it saves you from the most common way this goes bad. Add a server only when there is a real question you keep wanting answered from that system.
Because the failure mode is not that the servers do not work. It is that each connected server announces its capabilities into every session, and someone who connects nine of them has built a session that starts out already carrying a catalogue of hundreds of tools it will mostly never touch. You lose context and you lose accuracy, because the model now has to choose among hundreds of options rather than a handful. Two well-chosen connections beat nine speculative ones, easily. Pick them by the question, not by the availability.
So: put it all together, in the repository, in the order you would actually do it.
Start with the hook, because it takes ten minutes and it is the one that changes your day: after a write, format the changed file and run the typecheck, fast and silent on success. Add the second hook next, the one that inspects a write to the migrations folder and refuses only if the migration has already been applied, with a message that says which file and why. Then write one custom slash command — the diff review, or whatever the paragraph is that you retype most — and commit it, and tell the team it exists. Then one skill, for the pattern you have the most opinions about, with a description written as the concrete condition and a real example file sitting next to the doctrine. Then, and only when you next hit a question that wants forty files read, delegate that one to a subagent instead of answering it in your working session. Then connect one MCP server, for the one system you keep pasting from.
Now go back to the memory file, because this is the step people skip and it is where the whole episode pays off. Everything that moved into a hook or a skill should come out of the memory file. The formatter instruction: delete it, the hook owns it. The migrations warning: delete it, the hook enforces it. The route handler doctrine: delete it, the skill loads it when it matters. What is left in the memory file is what it was always supposed to be — the small set of facts that are true in every session, whatever you are doing. How to run the tests, where things live, the two conventions with no natural home elsewhere. If your memory file did not get shorter today, you did not finish.
And here is the pitfall you will hit, so I want you to be able to recognize it by the symptom rather than by feeling vaguely dissatisfied.
Everything in this episode is cheap to add, which means in about a month you will have eleven hooks, six skills that all half-apply to everything, four servers connected, and a session that feels slow and noisy and wrong in a way you cannot point at. That is over-wiring, and the specific tell is this: a failure now has three possible authors and you cannot say which. Something did not happen. Was it the model, ignoring the guidance? Was it a hook that blocked the action and got misread? Was it a permission rule from last episode that quietly denied it? You are looking at the same symptom in all three cases, and if you cannot tell them apart you cannot fix any of them.
When you get that feeling, the diagnostic move is blunt and it works: strip back to nothing. Disable the hooks. Take the skills out. Drop the servers. Run the failing task on a bare session and see what happens. Then add one layer back, and run it again, and the layer that reintroduces the failure is your answer. It sounds crude. It takes twenty minutes and it is faster than an hour of staring.
And the prevention is a rule about size rather than count. Keep every one of these artifacts small enough to read in one sitting. A hook that is thirty lines is a hook you can reason about in ten seconds; a hook that has grown into a shell script with branching is a program, and it needs to become a real file in the repository with a name and a test, not a growth inside a settings file. Same for skills: if the instruction file has become long enough that you would not read it, the model's attention is not going to fix that. Split it or cut it.
One more, and this one is not really about wiring. Every artifact you added today is a claim about how this project works. Claims rot. Six months from now the formatter has changed, the route handler convention has moved on, and the skill is confidently teaching the model the old way. A wrong instruction in a repository is worse than no instruction, because it is followed. So when you change a convention, grep your Claude directory. It is a five-second habit and it is the difference between this layer aging into an asset and aging into a trap.
Everything today lives inside one session, driven by you, and every single one of these five is a piece the rest of this show gets built out of.
The releases worth your attention this week
The CLI is at version two point one two six one as of the fourth of September, and it has been shipping most days through late August, so if you have not restarted the binary in a fortnight you are several dozen versions behind.
The headline item lands directly on what we just built. There is a new diagnostic command, slash skill doctor, and what it does is tell you which of your skills are eating an unreasonable share of your context. Given that half this episode was about not paying for guidance you are not using, that is the first thing to run once you have written two or three skills. Next action: write your skills, then run it, and treat any skill that is expensive when it should be dormant as a badly written description rather than a badly written body.
Two smaller CLI items in the same release. The diagnostic output — both slash status and the doctor command — now explains it when an organization policy file fails to load, which was previously a silent and genuinely maddening failure. And there are two new settings that raise the ceiling on how much output gets captured from a shell command and from a background task. If you have ever watched a test run get truncated at the interesting part, raise the command one and stop working around it.
Recent releases also added a fullscreen side-by-side diff view on the slash diff command, which is worth a try if you have been reading diffs in a cramped terminal pane. The cost display and the statusline now report prompt cache misses, which matters more than it sounds — a cache miss is money, and until now you had no way to see them. There is a command to reload plugins without restarting, for headless and desktop sessions. And late-August releases opened a research preview of a design command that generates editable interface artboards and then implements the option you pick.
One security change to know about even though it will not touch most people. Project-level settings can no longer use their environment block to redirect where configuration or temporary files live, and custom certificate handling, proxy rerouting or injected request headers now require explicit approval. That is a supply-chain fix: a checked-out repository could previously reconfigure your client on your behalf. If you clone strangers' repositories and run sessions in them, this one is for you.
On models, the first of September brought two new ones, Fable five point one and Mythos five point one. Both default to a million tokens of context, up to a hundred and twenty-eight thousand output tokens, with adaptive thinking always on. Pricing is ten dollars per million input and fifty per million output, and the number that should interest you most is the cache read price, which came down to twenty-five cents per million — a quarter of what the usual cache multiplier gives you. If your work is long sessions over a large repository, that is where your bill actually lives, and it just got substantially cheaper. One API-level gotcha for anyone building against these: forcing a tool call is no longer permitted on them, so a request that pins tool choice to any or to a specific tool comes back a four hundred. Use automatic choice or structured outputs instead. Two of the very latest patches also fixed a cache-miss regression specific to tool loops on Fable five point one, which is another argument for updating today.
On limits, the temporary summer capacity bump is expiring, and a twenty-five percent increase over the original baseline is becoming permanent. Net effect if you got used to the summer ceiling: you are coming down, just not all the way.
And in the surrounding tooling, the platform command line tool reached version one point thirty on the third of September, and it added a command that provisions agents, skills and memory environments as code, recording what it created into a lockfile. That is the infrastructure-as-code idea applied to the exact artifacts we spent today writing by hand, and it is worth a read now so it is familiar later.
Two community items, both aimed squarely at the token cost of a session that keeps rereading your codebase. One is a skill that indexes a repository into a local knowledge graph and answers dependency questions from a parsed structure rather than by reading files, and it ships an installer that wires itself in as a git hook. The other is a server backed by a small local database that exposes something like twenty-five tools for semantic search, blast-radius analysis on a pull request, and sub-second incremental reindexing. If your sessions burn their whole window on discovery before they do any work, spend an evening on one of these. There is also a small coding skill going around that enforces strict minimalism — standard library, no unrequested abstractions — purely to cut output tokens, which is an unusually direct way to attack the bill.
