Skip to main content

Claude Code rules, hooks and skills, and how I set mine up

8 min read

Claude Code gives you three places to put things that shape how it works, and it took me a few months to figure out how to optimize them. Rules are prose it reads, skills are procedures it follows when a task calls for them (or can be invoked manually), and hooks are rules (and code) that run at various steps (before/after tool calls, session start, etc). Each is appropriate for different things and getting that wrong is how you end up with a rule the model doesn’t follow or a hook that fires on work you actually wanted to do.

I started with CLAUDE.md by writing a core set of principles and rules, then added to it whenever something went wrong that I didn’t want going wrong again. Over a few weeks, that grew too much and turned out to cover a much wider range of things than I expected: standards, review criteria, permissions, writing style, the way sessions hand off to each other. It became clear there has to be a better way.

Rules

Rules are also plain English, and mine read like notes to a colleague who is fast, knowledgeable, but has no idea how I like to work. That last part is the most important part. I’ve been writing software for 15+ years and had never needed to plainly state for myself what I consider an acceptable amount of abstraction, I just recognized it when I saw it. A model can’t recognize it and can’t infer it, so it has to be told, and telling it forces you to find out whether you actually know.

Broadly, mine cover how I want work approached: plan before code, give me multiple options with tradeoffs for each (this by itself makes the model cover more cases and return at least slightly better output), or don’t reverse a position just because I pushed back, what I want checked when reviewing, language-level conventions, a separate set for anything a reader will see (strings, copy, etc) and many more.

The tip I’d pass on is to gate them by path rather than loading everything all the time. Context is finite and shared with the actual work, so a rules file that’s always loaded competes with the thing you asked for and might even be detrimental. Mine load per directory (when Claude edits a file in a given folder), which means, for example, that the Swift conventions are loaded while I work on Reckon or Continuum and aren’t while I edit markdown files.

Rules are one of the most useful things in the setup, but they rot exactly like code, with none of the tests to catch issues. If you happen to write a wrong rule, it’ll be there until you figure it out, or it’ll pollute forever and be forever used wrongly, so I recommend reviewing them every once in a while.

Skills

A skill is a flow or set of instructions you don’t want to explain multiple times or invoke over and over. The trigger for writing one, for me, is noticing that I’m describing the same sequence of steps again: not the same preference, which is a rule, but an actual ordered process with a beginning and an end.

For example, mine handle things like running a code review against a checklist and writing the findings to a dated file, wrapping up a session, and drafting a post in my own voice. They’re also a convenient place to put instructions like “read these other files first”, which is how my writing rules get loaded when they’re relevant and stay out of the way when they’re not.

The wrap-up one is a good example of how a skill grows. It started as just a session summary, written into a journal that lives with the project; now it also marks what got resolved in that day’s review file, refreshes a few rolling histories, then commits the session’s changes and deletes the worktree it worked in (a separate working copy of the repo). None of the later steps were something I’d let it do at the start; they became reasonable as the harness got tighter, because once the hooks below block the destructive commands, letting it commit or delete a worktree on its own stopped being something to worry about.

Hooks

Skills, rules and even CLAUDE.md are just hints or advice. Ultimately, if Claude decides it doesn’t want to follow an instruction, it won’t and on top of that, the longer a conversation, the more likely it is they might end up ignored.

Then there are hooks, which are the answer to the things a written rule can’t hold and the actual enforcing layer, and they do two fairly different jobs for me.

The first is removing friction. For example, I don’t want to use “bypass permissions” mode, but I also don’t want to manually approve every command, especially the dozens of harmless read-only ones; approving that many prompts a day means you stop reading them, which is worse than not being asked, because now you have a habit of clicking yes. So I have a hook that runs a script before any tool the model uses, which auto-allows the routine stuff and leaves the rest to prompt (for example, I don’t want any git merge command to auto-pass, but the ones I always use I want to not even prompt, so I have a hook that automatically approves something like git merge branchX release).

The second is enforcement. As mentioned above, a rule is advice, and advice can be forgotten halfway through a long session; I’ve watched it drift off a workflow rule it had been following correctly earlier in the same session. A hook runs regardless of what the model currently believes it agreed to. Mine blocks reads and writes to credentials, keys, personal directories, and a few genuinely destructive commands, the kind that delete uncommitted work with no way back.

As a third layer, there’s the sandbox setting: you can allow or disallow reads and writes to certain paths, at the system level, so these are hard denies, but they are also the ones that might break your workflow. For example, you want to deny a read to ~/.gnupg, since it contains your private signing keys, but if you want Claude to commit for you, it has to read there, somehow. So git commit itself has to be added as an excluded command from sandbox. But that means it can now read ~/.gnupg, so something fancy like git commit -F ~/.gnupg/path/to/private-key && git log will output your private key in the session (or exfiltrate it). So this is combined with another hook, that denies git commit -F (at least that’s the short version of it).

Lastly, never let it fail destructively. If the hook itself errors or can’t be read, the outcome should be a prompt, not a pass. Mine is written so a broken script emits nothing and the tool asks, which is the safe direction to fall.

Which one to reach for

The rule of thumb I ended up with: if you’d be content to be asked again, write a rule. If you’d want it caught on a day when you’re tired and not reading carefully, write a hook. If it’s a sequence rather than a preference, make it a skill.

What actually transfers

I don’t think you should copy someone else’s setup, mine included, because the files are the residue of specific things going wrong and the reasons don’t travel with them. What does transfer is smaller: gate what’s loaded, expect your rules to rot, enforce the things you can’t afford to have forgotten, and write down the limits of whatever you build so future-you doesn’t over-trust it.

The most recent addition to mine is a good example of how they actually arrive. I was planning this series and realized nothing in the setup knew anything about how I want posts to be discoverable; every existing rule was about voice, and SEO had never come up. So I put everything I needed in a rule file and gated it to the same paths as the other writing rules. Now, when I want to write a post, not only do my writing rules load automatically, discoverability and SEO rules do as well.

The next post is about who makes the decisions, because that’s a very important topic. Hint: it should always be you.

I’m curious what other people’s rules and hooks look like: @roland.leth.ro or @rolandleth.

The rest of the series is listed in What changed my mind about building with AI.