Skip to main content
  1. Posts/

Hooks are guarantees, skills are knowledge, subagents are other people.

··1101 words·6 mins·
Nick Liu
Author
Nick Liu
Building infrastructure for Facebook Feed Ranking at Meta. Previously at Walmart, Twitter, AWS, and eBay. MS in Computer Science at Georgia Tech.
Table of Contents
Taming Claude Code Sessions - This article is part of a series.
Part 6: This Article
My Claude Code config now holds two hooks, ten skills, and three custom subagents, and most of them started life in the wrong layer. The instruction the model followed nine times out of ten lived in a prompt until I accepted that nine out of ten is a coin I lose every day. The workflow I pasted into chats became a skill. The bulk work that was draining my priciest model's quota became a fleet of cheaper agents. Same features, different failure modes.
Tested with Claude Code 2.1.x 路 macOS

The four layers
#

Claude Code has four extension points, and they answer four different questions:

LayerQuestion it answersWho decides it runs
CLAUDE.mdWhat should the model always know here?Nobody; it is always in context
HookWhat must happen every time, no exceptions?Your shell script, deterministically
SkillHow do we do X around here?The model, when it judges X is relevant
SubagentWhat deserves its own context, tools, or model?The model delegates, or you ask it to

The column that matters is the last one. Every misplacement I have made comes down to ignoring who actually decides whether the thing runs.

Hooks: when it must happen every time
#

A hook is a script that fires on an event: session start, before a tool call, after one. No model judgment anywhere in the path. That makes it the only layer that can make promises.

My test case was session naming. Part 2 of this series covers the hook itself; the short version is that I wanted every session titled by project and branch so I could find it again. As a habit, it decayed. As a prompt instruction, it worked most of the time, and “most of the time” is exactly the property that makes a pile of unnamed sessions. As a SessionStart hook, it has run on every session since, including the ones launched over SSH and from tools that never show me a prompt at all.

The rule that fell out of this: if you would be upset by one missed execution, it cannot rely on model judgment. Naming, notifications, permission gates, lint-before-commit. Hook territory, all of it.

Skills: when it is knowledge, not a guarantee
#

A skill is a folder of instructions the model loads when the task looks relevant. It is the right home for how-to knowledge that would bloat CLAUDE.md if it sat in context permanently.

The clearest example in my setup is this blog. Writing conventions, shortcode rules, a sourcing policy, a pre-publish checklist: around two hundred lines. They matter only when writing a post, which is a small fraction of my sessions. As a skill, those lines cost nothing until the moment they are needed. My dotfiles repo went through the same migration; its CLAUDE.md keeps a short list of hard rules and points at six skills for the detailed workflows.

The trap runs the other way. A skill triggers on the model’s judgment of relevance, which means a skill is a suggestion with good packaging. When I catch myself writing MUST in a skill description, that is usually the tell that the thing belongs in a hook instead.

Subagents: when the work needs its own context, or a cheaper model
#

A subagent runs in a separate context window, with its own tool permissions and, since this is the part that changed my usage bill, its own model.

I run the main loop on the most capable model available and kept burning through its usage limits, because that model was also doing the grep runs, the batch edits, and the checklist verification. So I defined three agents in ~/.claude/agents/: a bulk worker and a scout on a mid-tier model, and a verifier on the cheapest one. Per Anthropic’s model pricing, the cheapest tier costs a tenth of the top one per token. An eight-point pre-publish check on this very post ran on the cheap verifier; the transcript that check produced never touched my main context window either, which is the second, quieter benefit.

One gotcha from setting this up: agent definitions register when a session starts. I created the files, dispatched work to them in the same session, and got “agent type not found.” Restart, and they were there.

The decision, as a flowchart
#

yes

no

yes

no

yes

no

yes

no

Must it happen
every single time?

Hook
(deterministic script)

Is it needed in
every session?

CLAUDE.md
(always in context)

Is it knowledge the model
should load on demand?

Skill
(loaded when relevant)

Does it need isolated context,
different tools, or a cheaper model?

Subagent
(own context and model)

Just put it in the prompt

yes

no

yes

no

yes

no

yes

no

Must it happen
every single time?

Hook
(deterministic script)

Is it needed in
every session?

CLAUDE.md
(always in context)

Is it knowledge the model
should load on demand?

Skill
(loaded when relevant)

Does it need isolated context,
different tools, or a cheaper model?

Subagent
(own context and model)

Just put it in the prompt

Worked examples from my own config, one per branch: claude-name-session had to happen every time, so it is a hook. “Use yadm, not git” applies to every dotfiles session, so it lives in that repo’s CLAUDE.md (memory docs). Blog conventions load on demand, so they are a skill. Checklist verification wants a cheap model and a disposable context, so it is a subagent. And a one-off “rename this variable” needs none of the above.

Lessons
#

  • The layer question is “who decides this runs,” not “where does config go.” Hooks decide with code, skills and delegation decide with model judgment, CLAUDE.md never decides at all.
  • If one missed execution would annoy you, it is a hook. Writing MUST in a skill description is the smell that you picked the wrong layer.
  • Skills are for knowledge that is expensive to carry and cheap to load. If it applies to every session, promote it to CLAUDE.md; if it applies to one task family, keep it a skill.
  • Subagents are a pricing feature as much as a context feature. Route mechanical work to cheap models and keep the expensive context for judgment.
  • Agent definitions load at session start. Create, restart, then dispatch.

References
#

Taming Claude Code Sessions - This article is part of a series.
Part 6: This Article

Related

Stop burning your best model's tokens on grep

··1070 words·6 mins
I kept slamming into my Claude subscription's usage limits, and the workload didn't feel heavy enough to explain it. The culprit was allocation, not volume: my main session ran on the most capable model available, and that model was also doing the grep runs, the batch edits, and the yes/no verification passes. Flagship reasoning, billed at flagship rates, spent on work a model a tenth the price does identically well. 馃И Tested with Claude Code 2.1.x 路 macOS Where the tokens actually go # Two things surprised me when I looked at my consumption honestly.

Your First Claude Code Hook: Auto-Name Every Session

··915 words·5 mins
In Part 1 we learned that named sessions are easy to find. Now let's make naming automatic with a hook. This is also a perfect first hook project, so I'll explain the whole idea from scratch. Taming Claude Code Sessions 路 Part 2 of 6 1 2 3 4 5 6 馃И Tested with Claude Code 2.1.x 路 macOS / Linux Part 1 left us with one chore: you still have to remember to name your sessions. Let’s delete that chore.

Holding HTTP open for 590 seconds so a Stream Deck key can approve a tool call

··2355 words·12 mins
Claude Code wants to run a shell command. I want to press a physical Stream Deck key, the YES key two inches to the left of my keyboard, to approve it. The hook gets exactly one HTTP response to decide allow vs deny. The key press might land in 200 milliseconds; it might land seven minutes later, after I've been pulled into a meeting and come back. The trick is that Claude Code's hook timeout is 600 seconds, which turns out to be just enough headroom to hold the HTTP response open the whole time and let a hardware button write the answer. Building ClaudeDeck 路 Part 6 of 10 1 2 3 4 5 6 7 8 9 10 馃И Tested with Claude Code 2.1.x 路 macOS (Setup, for anyone who hasn’t seen this stack before: Claude Code is Anthropic’s terminal CLI for Claude, and one of its hook events, PreToolUse, is a script Claude spawns and waits on before running a tool like Bash or Edit. The script’s stdout decides “allow” / “deny” / “ask”. Stream Deck is Elgato’s USB grid of programmable LCD keys. The plumbing I’m describing here lives in a daemon, a background process at 127.0.0.1:9127, that the hook script POSTs to and that the Stream Deck plugin connects to over WebSocket. For the hooks docs themselves and the four other gotchas in that layer, see the hooks-reality post.)