Custom Commands, Hooks & Subagents Best Practices
These are the conventions worth adopting once a project starts relying on custom commands, hooks, and subagents beyond a one-off experiment.
Each rule below reflects a real failure mode, an ambiguous matcher, a vague task prompt, a hook that runs too broadly, that shows up quickly once these extension points are used for real work.
How to Use This List
- Treat the rules as defaults to deviate from deliberately, not laws, since every project's risk tolerance differs.
- Revisit the hooks and commands sections whenever
settings.jsonor.claude/commands/grows past a handful of entries; conventions decay fastest under quiet accumulation. - Apply the subagent rules per task, since the right amount of delegation varies with how independent and large the work actually is.
- Pair the PreToolUse rules with an actual list of protected paths for your project; a rule without a concrete target does not enforce anything.
A - Naming and Discoverability
- Give every command a
descriptionin its frontmatter. Without it, a command is invisible in autocomplete, and teammates fall back to re-implementing the same request from scratch. - Name commands after the action, not the requester.
/pre-reviewreads clearly months later;/chris-checkdoes not survive the person leaving the team. - Keep command filenames and slugs stable once shared. Renaming a command breaks every teammate's muscle memory and any documentation referencing it by name.
- Group related commands with a shared prefix when the set grows.
/pr-review,/pr-summarize, and/pr-changelogread as a family in autocomplete; three unrelated names do not.
B - Scoping Hook Matchers
- Match hooks to the narrowest tool set that does the job. A formatting hook needs
Edit|Write, not an unscoped match on every tool call. - Prefer a dedicated script file over a long inline command in settings.json. A script is testable and diffable on its own; a sprawling one-liner in JSON is not.
- Guard against missing or malformed event data before acting. A hook that assumes a file path is always present will fail loudly, or silently, the first time it isn't.
- Keep hooks fast. A PostToolUse hook runs on every matching edit; a slow one (a full test suite, a large build) adds that latency to every single edit, not just the ones that matter.
- Version-control every hook script alongside the project. A hook that only exists on one contributor's machine is a hook the rest of the team doesn't actually have.
C - Using PreToolUse for Real Boundaries
- Reserve PreToolUse for rules that must hold even against a misleading prompt. Anything phrased as "this should never happen" belongs here, not in a PostToolUse cleanup step.
- Emit an explicit block decision, not just a non-zero exit code. A structured
{"decision": "block", "reason": "..."}response is the reliable signal; exit codes alone can be interpreted inconsistently. - Write block reasons for a human reading the transcript later. "Blocked" explains nothing; "this path is protected, ask a human to make this change directly" does.
- Anchor protected-path patterns to real directory structure, not just a file extension. A pattern like
*.jsonprotecting one lockfile ends up blocking every JSON file in the project. - Pair a PreToolUse guard with normal repo-level protections. A hook only fires within Claude Code's own tool-call path; it is not a substitute for branch protection or file permissions enforced elsewhere.
D - Delegating Work to Subagents
- Write subagent task prompts as if the subagent has seen nothing else. Because it hasn't; anything the parent session knows and doesn't restate is simply unavailable to the subagent.
- Scope a subagent's tools to what its job actually requires. A research subagent that never edits files should not have
EditorWritein its tool list, as a matter of enforcement, not just tidiness. - Ask for a summary, not full file contents, in the report-back instruction. A subagent that dumps whole files back to the parent defeats the context-saving purpose of delegating in the first place.
- Only fan out subagents in parallel when the pieces are genuinely independent. A task where one subagent needs another's findings first should run sequentially, not concurrently.
- Match the number of parallel subagents to how much synthesis the main session can actually do afterward. Spawning more subagents than you can meaningfully read and combine just delays the answer.
- Reserve subagents for tasks large or isolated enough to justify the overhead. A one-line lookup rarely needs its own context window; a forty-step exploration usually does.
E - Keeping the Three Extension Points From Colliding
- Don't ask a hook to make a judgment call. A hook has no model reasoning attached to it; if the decision requires weighing context, it belongs in a command's prompt or a subagent's task, not a shell script.
- Don't use a command where a hook would guarantee the outcome. If something must happen every time with zero exceptions, a command that a user has to remember to type is the wrong mechanism; use a hook instead.
- Document which of the three extension points a new automation uses, and why. A team member debugging unexpected behavior needs to know quickly whether to look in
.claude/commands/,settings.json, or a subagent definition. - Review
settings.jsonhooks and.claude/commands/together during onboarding. Both are checked into the repo and both shape what "typing a request" or "an edit happening" actually does; skipping either leaves a new teammate with an incomplete picture.
FAQs
Which of these rules matters most for a small project just getting started?
Giving commands a clear description and scoping hook matchers narrowly. Both are cheap to do from the start and expensive to retrofit once a project has accumulated a dozen undocumented commands or an overly broad hook.
Is it ever fine to skip writing a description for a command?
For a purely personal, throwaway command, yes. For anything checked into a shared repo, skipping it means teammates cannot discover the command exists without opening the file directly.
Why does the naming section warn against naming a command after a person?
Because a command outlives the person who wrote it; a name tied to the action (/pre-review) stays meaningful long after a name tied to the author (/chris-check) has lost its context.
What's the single most common mistake with hook matchers?
Leaving the matcher unscoped, so the hook fires on every tool call instead of just the tools it actually cares about, adding noise and latency to unrelated actions.
Why does the PreToolUse section insist on an explicit block decision instead of just a non-zero exit code?
Because exit-code handling can vary by configuration, while an explicit {"decision": "block", "reason": "..."} payload is the unambiguous, documented way to signal a block.
Should every protected file get its own separate PreToolUse hook entry?
No. A single guard script with a list of protected path patterns is easier to audit as a whole than the same rules scattered across many separate hook entries.
When is it acceptable to skip scoping a subagent's tools down?
When the subagent's task genuinely requires the same broad access as the parent session, such as a subagent explicitly tasked with making a coordinated set of edits across several files. Scoping is a default, not an absolute requirement.
What happens if I fan out subagents for a task that turns out to have a dependency I missed?
The subagent that needed the missing information will typically produce a weaker or incorrect report, since it has no way to request that context mid-task; catching dependencies before spawning avoids this.
Why does the list warn against asking a hook to make a judgment call?
Because a hook is a shell command with no model reasoning attached; asking it to "decide" something means writing brittle conditional logic to approximate a judgment call that a command or subagent could actually reason about.
How do these best practices change between a solo project and a team project?
The rationale stays the same, but the cost of skipping them rises sharply on a team: an undocumented command or an overly broad hook affects everyone who pulls the repo, not just the person who wrote it.
Is there a best practice for reviewing hooks and commands over time?
Revisit them whenever settings.json or the commands directory grows past a handful of entries, since conventions tend to decay quietly as more automations accumulate without anyone auditing the whole set together.
Do these practices apply equally to project-scoped and user-scoped commands?
Naming and description discipline matters for both, but version-controlling scripts and documenting which extension point is in use matters most for project-scoped commands and hooks, since those are what a whole team shares.
Related
- Custom Commands, Hooks, and Subagents: What Each One Is For - the foundational distinction these practices build on.
- Writing Your First Custom Slash Command with Markdown Frontmatter - where the naming and description conventions apply directly.
- Blocking Risky File Edits with a PreToolUse Hook - a full working example of the PreToolUse practices above.
- Spawning Parallel Subagents for Isolated Research Tasks - the fan-out practices in a complete walkthrough.
- Common Hook Events and When to Use Each One - matching a hook's event to its actual job.
Stack versions: Written against the Claude model lineup current as of ~June 2026 - Claude Fable 5, Claude Opus 4.8, Claude Sonnet 5 (the default), and Claude Haiku 4.5. Model names, pricing, and product features move quickly - verify current specifics at platform.claude.com/docs before relying on them.