This is article 4 in the series "The Art of Not Reading." It lays out symptoms that go wrong and their remedies, one at a time. Each article is finished once you put down a single file or script. Why that mechanism is needed becomes clear when you read the explanation afterward. The whole picture and the list of articles are in the introduction.
This time it is skills. A skill is a way of registering custom commands and tools with the AI. The way things dilute as you add them, from the introduction, happens with tools as well and not only with rules. What is different is that the thing doing the diluting is the very mechanism you wanted used. The more skills you line up, the less they get chosen.
Start by counting just one thing.
How many skills (custom commands, tools) do you have registered with your AI? Of those, how many did the AI pick out and use on its own over the past week?
If the number used is far below the number registered, the problem is not how you wrote them. A skill is a tool registration that carries a condition, "use this when such and such," and it hands the decision of whether to use it to the AI. The cost comes twice. Registering one is already enough to grow what gets read, and on top of that it does not get chosen.
The more similar conditions stand side by side, the more they eat into each other, and the one you most want used is the one that gets buried. What the introduction called "it dilutes as you add" is happening with tools as well, not only with rules.
The production project on my own machine, typingtube, a web service for practicing typing along with music videos on YouTube, has 84 scripts that I have the AI run (verifying the translations for more than ten languages, the design ratchet, benchmarks, generating character images ... there is a backstage tool for every feature you can see on the screen). Registered skills: one. The descriptions for all 84 of them are resident in no session at all.
What I understood fits in one sentence.
The more conditions for using a skill you line up, the less it gets chosen. So do not make it choose: make it read before it uses.
CC BY 4.0
Why they get chosen less the more you line them up
The conditions on your skills are all loaded the moment a session starts. What loads is the name and the description, nothing more. The body is added to the conversation when the skill is called. The Claude Code documentation on skills cuts that description, together with when_to_use, at 1,536 characters, and puts a budget of 1% of the context on the listing as a whole. Whatever goes over the budget drops. Registering one means adding one more page to that competition.
So should the conditions be written short and thin? The official guidance says the opposite. The documentation on defining tools says to write extremely detailed descriptions, and that this matters by far the most. What it does, when to use it, when not to use it, every parameter one by one, 3-4 sentences at the very least. And the same page says to group related operations into fewer tools. Thick per item, few in number. The design that grows the count was never the recommended one.
Things lined up side by side weaken each other. In measurements that ask a model to find a target line in a long input, accuracy fell with a single semantically close distractor mixed in. The conditions on skills are exactly a collection of semantically close things. "When you run the tests," "when the tests fail," "when you fix CI" — the closer they sit, the harder they are to tell apart.
On top of that, the AI is the one choosing, all the way to the end. The moment you write "use this when such and such," the one deciding whether it applies right now is the side that reads it.
In a long session the choices themselves disappear. As the conversation approaches the limit of the context, Claude Code compacts the history. At that point the instruction files and the memory index are read back from disk. The skill listing, however, is not re-injected. The further into a long-running session it goes, the less the AI can choose what you registered.
The mechanism: instead of making it choose, make it read at the entrance
You leave the commands as plain scripts and write how to use them in the documentation. So far this is ordinary. What you put down is one hook: it stops only when the way in is wrong, and points at the one page that has to be read. It is the same shape as the wrapper guidance in article 1.
For commands that need setting up first, use a form that goes one step further. Take the pre-commit hook, the kind of mechanism that catches leaked secrets and convention violations on every commit, which assumes a git hook has been installed. Install it the way the procedure says and .git/hooks/pre-commit comes into being. The Claude Code hook checks only that this file exists when the AI types git commit, and if it is not there, it points at the procedure and stops.
#!/bin/bash
# .claude/hooks/require_setup.sh — stops unless the trace of the procedure (the file it generates) is there (a shortened version of the code on my machine)
command=$(cat | python3 -c 'import json,sys; print(json.load(sys.stdin).get("tool_input",{}).get("command",""))')
case "$command" in *--no-verify*|*SKIP_HOOK_CHECK=*) exit 0 ;; esac # let the explicit escape hatch through
printf '%s' "$command" | grep -qE '(^|[;&|[:space:]])git[[:space:]]+commit([[:space:]]|$)' || exit 0
hooks_dir=$(git rev-parse --git-path hooks 2>/dev/null || echo .git/hooks) # look at the right place even in a worktree
if [ ! -x "$hooks_dir/pre-commit" ]; then
echo "Pre-commit checks are not installed. Run the setup in docs/setup.md (bash scripts/setup_hooks.sh) first" >&2
exit 2 # do not let it run; make the AI read the guidance on stderr
fi
exit 0
This hook is not judging whether the documentation was read. Whether it was read cannot be measured. All it looks at is the existence of a file that is not created unless you look at the documentation. If the file is there, the procedure was followed, whatever route was taken.
The escape hatch opens only when it is explicit. What the code above lets through is SKIP_HOOK_CHECK= and --no-verify, the way past that git has had all along.
This works because an AI that is stopped goes looking for the documentation. It stops, reads the guidance, follows the procedure, goes on. Instead of choosing "when to use it" in advance, it becomes "if you use it, you read it; if you have not read it, you cannot move," and the single page that is needed gets read at the moment it is needed.
Before I moved to this shape, a working directory I had cloned sat with the pre-commit hook never installed, and 39 commits, and in another environment two months' worth, piled up without passing through a single hook. The frightening part is that nothing failed. Nobody notices what does not fail.
What I stopped reading
The pile of skill descriptions. I stopped writing them, stopped having the AI read them every session, and stopped tuning the conditions so that they would get chosen. How to use the 84 scripts sits in one page of documentation each, and it gets read only when a mechanism like a hook is judged necessary.
Caveat: this does not go on every entrance
This does not mean writing 84 hooks. The only entrances worth stopping are the ones where a mistake is expensive: in my environment that is running the tests, committing, and starting a subagent, a handful of places. The rest stay plain, because a mistake there shows up at once and is cheap. Put a guardrail on all of them and you have only rebuilt the whole set of conditions out of hooks.
How to verify: take it away, and confirm that it stops
Prerequisites
- You have finished registering the one hook from this article
- You have settled on the pair: the command to stop, and the file whose existence is checked (in the example above, when the AI types
git commitit looks for.git/hooks/pre-commit)
Time required: 5 minutes
Steps
- You move the file that is being checked for out of the way (something like
mv .git/hooks/pre-commit /tmp/). You move it aside, you do not delete it. You put it back in the cleanup - You have the AI run the command in question (in the example above, you ask it to "commit")
Pass conditions (all of them have to hold)
- It stops without the command running
- Where it stopped, the location of the documentation to read is shown
If it does not pass
- It went through without stopping — either the hook is not registered, or the path it looks at is not the file you moved aside in step 1
- It stopped, but no guidance appeared — the hook is doing only half its job. An AI that is not shown where to go starts looking for its own way around, right there
Cleanup
- Put the file you moved aside back where it was, and have the AI run the same command once more. You are done when it goes through without stopping this time
- If it still stops, the path the hook looks at and the place you put the file back do not match
A mechanism that stops things breaks in the direction of not stopping. A hook that has never once stopped anything looks the same as a hook that is working. Both of them let things through in silence.
From next time it is the advanced articles, starting with "Read your subagents' output, and only that." Subagent output is the one thing that goes badly wrong if you do not read it.
Series: The Art of Not Reading
- ← Previous: 3. The art of not reading unit tests
- → Next: 5. Read your subagents' output, and only that
- All articles: Introduction: I Barely Read What the AI Outputs Anymore
CC BY 4.0 はここまで