Daily Newsletter

Tool Showing 18 tips
#021 Claude Code
Hooks fire on system events β€” the model cannot prevent or delay them
Unlike prompt instructions Claude might forget, a PreToolUse hook is technically guaranteed to execute before every matched tool call. Hooks are deterministic enforcement, not model-dependent suggestions.
"Hooks are where Claude Code stops being 'helpful' and starts being reliable β€” the deterministic layer that formats, tests, and blocks risky behavior whether the model remembers to or not."
β†— Source
#022 Claude Code
Exit code 2 = block with reason to Claude. Exit 0 = allow. Exit 1 = hook error
Code 2 blocks the action and sends your stderr as context to Claude. Code 1 is a hook execution failure β€” different from a deliberate block. Most developers conflate 1 and 2.
"Return exit code 2 from a PreToolUse/PermissionRequest hook to deny the action and send your error message back to Claude."
β†— Source
#023 Claude Code
Async hook output arrives on the next conversation turn β€” not the current one
An async hook fires and Claude immediately continues. The hook's output arrives as context one turn later. Design async hooks so their output is still actionable one turn later.
"After the background process exits, that content is delivered to Claude as context on the next conversation turn."
β†— Source
#024 Claude Code
type: "prompt" runs an LLM as the hook decision-maker β€” almost nobody knows this exists
Beyond shell commands, hooks support type: "prompt" which runs a lightweight Claude evaluation. Perfect for nuanced Stop hooks: evaluate whether this task is genuinely complete. Not in the /hooks UI β€” JSON only.
"Prompt hooks use LLM evaluation (great for Stop/SubagentStop). Config only: Must be added by editing settings JSON directly. The /hooks interactive menu only supports command hooks."
β†— Source
#025 Claude Code
Matcher patterns are regexes β€” case-sensitive, no spaces around |
The matcher is a regex on the tool name. "Edit|Write" uses alternation. It is case-sensitive. Spaces around | break it silently. Wrong casing causes hooks to silently never fire.
"Critical: No spaces around |. Matchers are case-sensitive."
β†— Source
#026 Claude Code
File path filtering must happen inside the hook script β€” there is no declarative path matcher
Matchers only filter by tool name, never by file path. To block writes to .env, parse tool_input.file_path from stdin JSON inside your hook command and exit 2 if it matches.
"For tool-based hooks, matchers only filter by tool name, not by file paths or other arguments. To filter by file path, check tool_input.file_path inside your callback."
β†— Source
#027 Claude Code
MCP tool matchers must use the prefix mcp__<server>__<action>
To hook a specific MCP tool, use the full prefix format. Add a no-matcher PostToolUse hook that logs .tool_name to discover all tool names during a real session before building matchers.
"MCP tools always start with mcp__ followed by the server name and action: mcp__<server>__<action>."
β†— Source
#028 Claude Code
UserPromptSubmit injects context before Claude ever processes your message
A hook on UserPromptSubmit fires before the model sees your prompt. Return additionalContext JSON to automatically inject live context β€” Jira status, git diff, sprint priorities β€” on every single message.
"UserPromptSubmit β€” validate or enrich prompts before Claude processes them; you can inject text or structured context."
β†— Source
#029 Claude Code
SessionStart fires on startup, resume, clear, AND compact β€” each is matchable
You can match only compact to run context-recovery logic specifically during compaction β€” not on every session start. Most hook guides only show generic SessionStart.
"SessionStart: startup, resume, clear, compact PreCompact: manual, auto"
β†— Source
#030 Claude Code
type: &#34;http&#34; hooks POST event JSON to your URL β€” enables centralized org policy servers
Claude posts the event JSON to your endpoint and reads the block/allow decision from the HTTP response. Build one central policy server to enforce the same rules across every developer's Claude Code session.
"For HTTP hooks, it arrives as the POST request body. Your handler can inspect the input, take action, and optionally return a decision."
β†— Source
#031 Claude Code
Enterprise managed hooks cannot be disabled by users
If an admin configures hooks via managed policy settings, a user's disableAllHooks: true cannot override them. Only a disableAllHooks at the managed policy level can disable managed hooks.
"If an administrator has configured hooks through managed policy settings, disableAllHooks set in user, project, or local settings cannot disable those managed hooks."
β†— Source
#032 Claude Code
Async hook completion is suppressed by default β€” use --verbose to confirm it fires
When an async hook completes, its notification is hidden unless you launched with --verbose or toggled via Ctrl+O. If an async hook appears to do nothing, enable verbose mode first.
"Async hook completion notifications are suppressed by default. To see them, enable verbose mode with Ctrl+O or start Claude Code with --verbose."
β†— Source
#033 Claude Code
PreCompact hooks let you snapshot task state before context is lost
The PreCompact event fires before the compaction algorithm runs. A hook here writes a structured backup of current state and open decisions β€” a precise recovery artifact beyond the fuzzy prose summary.
"The backup system… Backups use numbered filenames with timestamps. When a backup exists for the current session, the statusline shows the backup path."
β†— Source
#034 Claude Code
Skills can define their own scoped hooks in YAML frontmatter
A skill's YAML frontmatter can declare hooks: that only fire when that skill is active. A database-migration skill can have its own PreToolUse hook validating read-only queries β€” zero global pollution.
"PreToolUse hook runs the script specified in command before each Bash command executes. db-reader … hooks: PreToolUse: - matcher: Bash hooks: - type: command command: ./scripts/validate-readonly-query.sh"
β†— Source
#035 Claude Code
Spawning subagents from UserPromptSubmit hooks can create infinite recursive loops
A hook that spawns subagents risks infinite recursion if those subagents also trigger UserPromptSubmit. Fix: check the hook input JSON for a subagent indicator. Always scope to top-level agent sessions only.
"A UserPromptSubmit hook that spawns subagents can create infinite loops if those subagents trigger the same hook."
β†— Source
#036 Claude Code
The /hooks UI only configures command hooks β€” prompt and HTTP hooks require JSON
The interactive /hooks menu only supports type: "command". The more powerful type: "prompt" and type: "http" variants must be added by editing settings JSON directly.
"Config only: Must be added by editing settings JSON directly. The /hooks interactive menu only supports command hooks."
β†— Source
#037 Claude Code
Default hook timeout is 10 minutes β€” sync hooks block Claude for their entire duration
Unless you set timeout in hook config, hooks can run for 10 minutes. A slow external API call in a sync PreToolUse hook stalls every matched tool operation for up to 10 minutes.
"If not specified, async hooks use the same 10-minute default as sync hooks."
β†— Source
#038 Claude Code
SessionStart now executes after Claude is interactive β€” not before first response
A recent optimization deferred SessionStart hook execution, reducing time-to-interactive by ~500ms. If your workflow assumed the hook completes before Claude can respond, that assumption is now incorrect.
"Improved startup performance by deferring SessionStart hook execution, reducing time-to-interactive by ~500ms."
β†— Source