Both a hook and a skill can make Claude Code "just do the thing" without anyone asking each time. That similarity hides the one difference that actually matters when you're deciding between them: a hook runs because the harness fires it on a matching event, every time, regardless of what the model thinks — a skill runs because the model reads its description and judges the current task matches, which means it can also judge wrong, or not read closely enough to fire at all. If a rule can never be skipped, that distinction is the whole decision.
A hook is a shell command bound to a lifecycle event — before a tool runs, after a tool runs, when the session stops — configured in settings.json. It executes outside the model's reasoning entirely: the harness sees the event, runs the command, and the model has no opportunity to decide the situation is an exception. That makes hooks the right container for anything phrased as "always," "every time," or "never skip this": always run the linter after an edit, never allow a commit that touches a secrets file, always log every tool call to an audit file. Memory, instructions, and skills all route through the model's judgment first — which is exactly the path a hard rule can't afford to take, because judgment is also where an unusual case slips through.
The tradeoff is that a hook can't read intent — it fires on the event it's bound to whether or not this particular instance actually warrants it, so an overly broad hook becomes noise (or a blocker) on cases you didn't mean to catch. Hooks are also configuration, not conversation: they need to be set up once in settings.json, they don't "learn" a new trigger from being told about it in chat, and getting the event/matcher wrong just means silent non-firing, not a wrong-but-visible attempt.
A skill is the right container when the trigger condition is a judgment call, not a fixed event — "when the user is about to write a chart, apply these color rules," "when a migration file is being written, run through this checklist." There's no discrete, mechanical event to bind to; recognizing the situation requires understanding what the task actually is, which is precisely what the model is for and a hook's matcher isn't.
The tradeoff is the one a hook doesn't have: no guarantee. A vague description, an unusual phrasing of the task, or a long conversation that's buried the relevant context can all mean the skill simply doesn't fire — with no error, because from the harness's point of view nothing went wrong. A skill is only as reliable as the model's ability to recognize when it applies, which is real but not absolute.
Ask: if this were skipped exactly once, would that be a shrug or an incident? An incident-grade rule — a security gate, a compliance requirement, a step whose omission breaks something downstream and silently — belongs in a hook, because only the hook is structurally incapable of being reasoned around. A shrug-grade rule — a style preference, a nice-to-have checklist, a "usually a good idea" — belongs in a skill, because it benefits from the model's judgment about when it actually applies and doesn't need airtight enforcement to be useful.
| Question | Answer points to |
|---|---|
| Would skipping this exactly once be a real problem, not just a missed nicety? | Hook |
| Is the trigger a discrete event (a tool ran, the session ended) rather than a judgment call? | Hook |
| Does firing correctly require understanding what the task actually is? | Skill |
| Is occasional non-firing an acceptable cost for not needing separate configuration? | Skill |
These aren't a single either/or: a Stop hook can enforce that a skill's checklist was actually completed before the session ends, catching the case where the skill should have fired but didn't. A skill's instructions can tell the model to rely on a hook-enforced gate rather than re-checking the rule itself, since the hook already guarantees it. The pattern from choosing between a skill, a subagent, and a slash command still holds one level up: figure out what property you actually need — guaranteed vs. judged, in this case — before picking the container.
This is a packaging decision, not a workflow one. The checklists in the Claude Code Power Pack and Claude Code Debug Pack are the procedures; see also skill vs. subagent vs. slash command for the other three containers a workflow can live in, git worktrees for parallel agents for the isolation question one level further out — not which container runs the work, but which filesystem it runs on — and running Claude Code headless in CI for what happens to the guarantee once there's no human to answer a permission prompt at all.