It's never the prompt
The second time I type the same instruction, it goes into a file Claude reads on its own.
“Okay, but what do you actually type?”
That’s the question I get, pretty much word for word, whenever somebody new to Claude Code asks how I use it. And it’s a fair question! The interface is a text box, so of course the text box is where you’d look. But the prompting has been the least interesting part of my own setup for a good while now. I don’t think that’s just me, either.
Instead I do something closer to a habit than a system, though calling it either one oversells it. The second time I catch myself typing the same instruction, I stop typing it and put it somewhere Claude reads on its own. Everything I actually type into the box is about the task in front of me.
Plans are worthless
Starting here because it costs nothing and it’s the one thing I’d push on anybody.
Plan mode goes off, reads around the codebase, and comes back with a plan before it has touched a single file. Is the plan better than what Claude would have built anyway? Sometimes it is! Often it’s the same work, described in advance.
But a plan is much easier to argue with than a set of file modifications. When the third paragraph says it’s adding an abstraction I don’t want, I point at the helper that already exists and that costs me a minute. When the same decision arrives as a diff across five files, either I take it as is or I make it A Whole Thing. By then there’s a sunk cost sitting on both sides of the conversation.
So it’s plan mode for anything with more than a couple of steps in it, and not much point in the ceremony for a quick fix.
I didn’t expect the plan to also be a verdict on me. If it comes back and I can’t tell whether it’s right, that isn’t Claude being unclear. It’s me not having decided what the work is or what done looks like. Finding that out before any code exists is a lot cheaper than finding it out afterward.
Kill your darlings
This one’s my favorite, mostly because of how I got to it. I have a commit skill because I was relitigating commit message conventions every few days.
It holds the mechanical stuff, like an imperative subject, a body that explains why instead of what, and no British spellings sneaking in. None of that is gospel. It’s a set of preferences I’ve built up over years of git and I’d rather write them down once than keep re-explaining them.
The part I actually care about is welded onto the front of it. Before the skill stages anything or writes a word of the message, it decides whether the working tree is substantial enough to want a simplify pass. If it is, those edits land in the same commit.
Use judgment, this is a gate and not a reflex:
- **Run simplify** when the diff adds or reworks real logic: new
functions or classes, non-trivial control flow, a multi-file
change, or more than a couple dozen substantive lines.
- **Skip it** for trivial or mechanical changes: typo fixes, config
bumps, lockfiles, pure renames, formatting-only diffs, one-liners.
- When it's borderline, lean toward running it.
Two things make that work. I think the second one matters more.
The first is what simplify catches. A model’s first draft tends to be correct and slightly too much: an extra layer of indirection, a helper that duplicates one sitting four files over, a function living a level lower than it needs to.
None of those are bugs. That’s exactly why they survive review, including mine. It’s how a codebase turns into a place nobody volunteers to work in.
The second is when it runs. Back when I was invoking simplify by hand it always ran against code I’d written who knows how long ago. I didn’t have much of an opinion about what it suggested either way.
The moment before a commit is the one moment I still have the changes in my head and haven’t started thinking about the next thing. My attention is the scarce thing here, not the tokens. So the pass is worth the most when it’s welded onto something I was doing anyway.
And the gate matters as much as the pass does. An early version ran on everything. You bump a version number and something goes off spending oodles of tokens contemplating your package.json before reporting back that the file looks fine.
Good fences
A skill is only a document sitting on a disk. It doesn’t fail by being wrong so much as by getting followed only most of the time. Some fraction of the time Claude writes a plain git commit and none of the above happens. I suspect it’s nothing more interesting than a long session getting crowded, with the skill file as one document among a lot of other things.
So there’s a PreToolUse hook that sees every Bash command starting with git. It looks for a real git commit subcommand and denies it unless the command carries a marker.
# Amends and fixups aren't new commits, let them through.
if printf '%s' "$command" | grep -qE '(^|[[:space:]])--(amend|fixup|squash)([[:space:]=]|$)'; then
exit 0
fi
# Skill-emitted commit, carries the marker.
if printf '%s' "$command" | grep -qE 'CLAUDE_SKILL=commit([^A-Za-z0-9_-]|$)'; then
exit 0
fi
The marker is a prefix that does nothing. It sets an environment variable for the git process and never touches the commit itself. It’s a token that proves the skill got loaded and nothing else.
I’m a little smug that the deny message never names the marker. It says raw commits are blocked and to go invoke the skill. That’s all it says.
So the only route past the fence runs through the file. By the time you’re through it you’ve read the conventions anyway. My sense is that the reading does more work than the block does. A fence that teaches you the rule while it’s stopping you doesn’t have to be right about much else.
Amends and fixups walk straight through, since a reword isn’t a new commit and the history-rewriting skills would be dead in the water otherwise. And if the directory isn’t a git repo at all, the hook shrugs and gets out of the way on the theory that git’s own error message is better than anything I’d write. That last one is a decision and not an accident. I spent six weeks this spring finding out what it looks like when a hook takes that exit on its own.
There are three siblings to it. One does the same job for gh pr create. Another checks git identity, but only inside the one directory that holds nothing but work repos.
It leaves everything else alone on purpose. Some work repos live under ~/Herd right next to personal ones, so outside that one directory the path tells you nothing. I’d rather have no hook than one that’s confidently wrong about who I am.
The third blocks commits on master, main, staging and dev. It’s the one that got fiddly in an interesting way.
The first version trusted the working directory the session reported. That’s fine right up until the session is standing in one repo and committing to a sibling with git -C, which I do constantly with our Shopify theme template. So it was blocking legitimate feature-branch commits because the directory the session happened to be standing in was on main, and waving through the ones that mattered.
Now it resolves the repo the command will actually commit to, honoring a leading cd and any -C, and asks that repo what branch it’s on. (The template itself is a whole other post, including the hook that lives inside it instead of in my global config.)
Permissions I leave on auto. The deny list is two entries, both of them docker prune. In Claude we trust 🙃
Please match this vibe
Claude Code is good at a lot of things and I don’t think front of the front end work is one of them. Ask for a nice page and you’ll get one. Ask for another one next week and you’ll get another one, nice in a slightly different way. By the fifth you have five color scales and a spacing system that’s less a system than a set of coincidences.
So now the design happens upstream in Claude Design, which hands back tokens and a set of components with real style objects behind them instead of a comp and a mood. The handoff to Claude Code stops being “here’s a vibe, please match it” and becomes “here’s the spec, please transcribe it.”
The token values at the top of this site’s stylesheet are a verbatim copy of the design system’s own token files. Every component’s CSS is a transcription of that component’s style object. The project’s CLAUDE.md carries both of those rules plus the one that makes them mean anything: go and read the component, never work from memory.
The change in the shape of the failures got me. When the build is wrong now, it’s wrong in a way I can check. This padding doesn’t match that token. That’s a bug with a correct answer instead of a taste argument I’m having with a language model.
The hour I already paid
I suspect everybody’s first CLAUDE.md is a pile of preferences, and mine is no different. It has American English, BEM, alphabetized CSS declarations, and a whole little ordering for HTML attributes with an exception for when the class value is a wall of Tailwind and belongs at the end instead. Useful, unremarkable, saves me retyping myself twice a week.
The gotchas earn their place because they’re the things no amount of reading the code gets you back. Every one of them is something that already cost me an hour once, written down so it costs nothing forever after.
This site’s file has a note that a Liquid assign inside an include leaks back out into the caller and survives into the next iteration of the enclosing loop. That’s why every card on the projects page was quietly wearing the previous project’s tag color. I would not have found that twice. I barely found it once.
The skills we use at work are the same story. The steps are the least valuable thing in any of these files, since they’re the part I’d have gotten right on my own.
It just piled up
The whole setup is noticing you’ve typed something twice and then spending 10 minutes on it. Written out in one post it looks deliberate, like something somebody sat down and designed. But it accumulated over months, one repetition at a time. I’d forgotten a fair bit of it was even there until I went looking to write this.
All of it got assembled around my projects and my habits. A lot of it would probably be actively wrong for somebody who works differently than I do.
I’d actually hand over something smaller than any of the files. The second time you type an instruction is the signal to go put it somewhere, not the fifth, when you’re finally sure it’s a pattern. I still miss it constantly and catch myself explaining the same thing for the third time in a week, so, y’know, do as I say.