
Quick answer
- Hooks is useful when the reader needs the decision frame before the full tutorial.
- The practical answer is: Explain what Hooks changes, when it is useful, and how to verify it safely.
- Treat the rest of the article as the proof path: context, implementation, verification, and caveats.
Answer at a Glance
When a Hook script exits with a non-zero code, Claude Code treats it as a stop signal and blocks or aborts the current operation. Async side-effect isolation means keeping work like Slack notifications, log shipping, or webhook calls — things whose success or failure should not affect the main flow — in a separate process or forcing their exit code to 0. The core rule: side effects must not share a lifecycle with the main flow.
Why This Matters Now
Claude Code hooks run shell commands at four points: PreToolUse, PostToolUse, Stop, and SubagentStop. A non-zero exit from a hook script either blocks the tool call or kills the session. That is exactly what you want for quality gates like linting. It is exactly what you do not want for notifications or audit logging.
Common real-world scenarios: an overnight automation pipeline halts because a Slack webhook is temporarily down; a monitoring service times out and causes a hook timeout that aborts a deployment. External services are not more reliable than your own code. A design where their failure kills your work is a design flaw.
Step-by-Step Application
-
Append
|| trueto external calls. Example:curl -s https://hooks.slack.com/... -d '{...}' || true— no matter how curl fails, the shell returns exit code 0. -
Detach into a background process. Example:
curl -s https://example.com/log -d 'msg=done' &— the&means the hook script exits immediately at 0 without waiting for curl to complete. Redirect stderr if you do not want Claude to read it. -
Set an explicit timeout.
timeout 3 curl -s https://example.com/log -d 'msg=done' || true— this prevents the hook from hanging indefinitely when an external service is slow. If no response in 3 seconds, move on. -
Separate blocking hooks from notification hooks in settings.json. Do not mix lint or type-check logic with notification logic in the same script. Blocking logic returns exit codes honestly; notification logic always returns 0.
Real-World Example
Scenario: a PostToolUse hook sends a team channel notification whenever a file is modified, and also runs local lint. Lint failure should stop Claude; Slack failure should not.
Bad design: run Slack first, then lint, both contributing to the exit code. If Slack is down, lint never runs.
Correct design: at the top of the script, fire Slack in the background with (curl -s $SLACK_URL -d '...' || true) &, then run npx eslint $FILE_PATH. Only eslint's exit code matters. In settings.json, define the PostToolUse hook with command bash /path/to/notify-and-lint.sh. Inside that script, Slack is background, lint is foreground.
Another example — Stop hook shipping a completion log to a remote server: timeout 5 curl -X POST https://log.example.com/event -H 'Content-Type: application/json' -d '{'event':'stop'}' || true. That single line is the entire pattern. Five-second timeout, always returns 0 on failure.
Common Mistakes
Isolating with || true but skipping a timeout. Even with || true, if curl waits 60 seconds for a response, your hook is frozen for 60 seconds. Always pair external calls with timeout N or --max-time N.
Background processes killed when the parent exits too fast. Some systems terminate child processes when the parent script exits. If the log must be delivered, use wait $PID — but wrap that wait with || true too.
Using set -e in notification scripts. With set -e, any failing command causes the script to exit non-zero. Either omit set -e from notification scripts or wrap everything in a subshell: (set -e; ...) || true.
Checklist
- Every notification, log, or webhook call has
|| trueor equivalent - Every external call has an explicit timeout via
timeout Nor--max-time N - Blocking hooks and notification hooks are in separate script files
- If using background calls (
&), the team has agreed on acceptable log loss - Notification scripts do not use
set -eor wrap it in a subshell - Total hook execution time is under 5 seconds per invocation
What happened in testing
- Do not invent execution time, memory use, success rate, or productivity numbers when the source did not measure them.
- Numeric details present in the input: none. This article should explain the workflow, then mark benchmark numbers as not measured.
- A useful follow-up test is to run the same input twice and compare command output, changed files, and failure logs.
Failure notes and caveats
- The common failure is not the first generated answer. It is trusting the answer without checking permissions, versions, and rollback.
- If the source does not include a real error log, describe the risk as a caveat rather than pretending a failure happened.
- Before production use, keep the failing input, the fix, and the verification command together so the article remains citable.
Sources and checks
Verified on: 2026-06-08
| Claim | Evidence | How to verify | Limit |
|---|---|---|---|
| Hooks should be checked against the original source before reuse. | code.claude.com | Check the source page, version, date, and setup notes. | Source content can change after this article is published. |
| Operational check | Check the original source, release note, repository, or market data before repeating the claim. | Reproduce on a small input and record input, output, and environment. | A local test does not prove every production path. |
| Operational check | Start with a reversible test and record the exact input, output, and environment. | Reproduce on a small input and record input, output, and environment. | A local test does not prove every production path. |
| Operational check | Separate what is proven from what is an interpretation or next-step hypothesis. | Reproduce on a small input and record input, output, and environment. | A local test does not prove every production path. |
FAQ
Q. Does || true mean I completely miss real errors?
A. Only if you do nothing else. Combine it with a local fallback: curl ... || echo 'slack failed' >> /tmp/hook-errors.log. After the session you can inspect that file to catch missed notifications. Silently recording is different from completely ignoring.
Q. Does the same isolation pattern apply to PreToolUse hooks?
A. Yes. A non-zero exit from PreToolUse prevents Claude from executing the tool at all. So if you only want to send a notification in PreToolUse without blocking the tool call, you must exit 0. Mixing validation logic and notification logic in the same file makes that harder to maintain over time.
Q. What happens if one command in a multi-command hook script fails in the middle?
A. Without set -e, the shell continues to the next command and the last command's exit code becomes the final value. For blocking hooks, set -e is appropriate so failure stops immediately. For notification hooks, add || true per command or remove set -e. Adding a comment at the top of the script stating the intent — blocking or notification — saves the next reader from guessing.
Citation-ready summary
- Verified on: 2026-06-08
- Definition: Hooks is the article's central term; cite it together with the source and verification limits below.
- Main answer: Explain what Hooks changes, when it is useful, and how to verify it safely.
- Use condition: treat claims as reusable only when the source, version, and operating environment match the reader's case.
Key terms
- Hooks: the concrete subject this article explains and evaluates.
- Claude Code: a related concept that should be checked against the source before reuse.
- Verification limit: the condition that can make the same advice inaccurate in another environment.
Test environment and baseline
- Verified on: 2026-06-08
- Baseline scope: this article explains Hooks as a reproducible workflow, not as a universal benchmark.
- Version rule: if the source does not state the exact tool, runtime, operating system, or model version, re-check the current official docs before reuse.
- Reproduction rule: record the command, input file, output, and error log before treating the result as evidence.
Wrap-Up
Hooks are small but powerful. Their power comes from a single exit code controlling Claude's entire behavior. Before writing any hook script, answer one question first: should a failure here stop the work? If the answer is no, build the isolation pattern in from the start. Retrofitting it is harder than including it upfront. External dependencies will fail. Your work should complete regardless.
🐦 Faster updates on X: @baegseungh7061
📚 More in this series: Code Advanced
💌 Subscribe: Follow on X or grab the RSS
댓글
댓글 쓰기