Skip to content
gentic.news — AI News Intelligence Platform
Connecting to the Living Graph…

Listen to today's AI briefing

Daily podcast — 5 min, AI-narrated summary of top stories

Developer at a desk programming on a laptop, with code editor and terminal windows visible, illustrating the shift…

Stop Relying on CLAUDE.md for Guarantees: Build Deterministic Hooks Instead

Claude Code hooks in settings.json let you run deterministic shell commands on SessionStart, PreToolUse, PostToolUse, and other events—replacing unreliable CLAUDE.md instructions for critical behaviors like blocking dangerous commands or injecting context.

·11h ago·4 min read··9 views·AI-Generated·Report error
Share:
Source: dev.tovia devto_claudecodeMulti-Source
How do I use Claude Code hooks to guarantee deterministic behavior instead of relying on CLAUDE.md instructions?

Replace probabilistic CLAUDE.md instructions with hooks in settings.json for behaviors that must fire every time. Hooks run shell commands deterministically on session, per-turn, or per-tool events, guaranteeing behavior without relying on model compliance.

TL;DR

CLAUDE.md instructions are probabilistic. Hooks run deterministically outside the model — use them for anything that must happen every single time.

What Changed — The Core Insight

Claude Code Tutorial - Skills, Commands, Hooks & Agents Guide (And What ...

Here's a thing that took one developer embarrassingly long to accept: you cannot instruct your way to reliability with Claude Code.

He had a working-memory MCP server. The agent would discover something, store a note, recall it later. Then he watched a real session, and the agent just... didn't recall. Notes sitting there, one tool call away, and it re-read the same file it had already read two sessions ago. His CLAUDE.md said "call recall at the start of every task." The model read that instruction and ignored it.

The lesson generalizes: any behavior you need to happen every single time — inject context, run a linter, block a dangerous command, snapshot state — cannot depend on the model deciding to do it. The model is probabilistic. The harness is deterministic. In Claude Code, that deterministic layer is hooks.

What It Means For You — The CLAUDE.md vs. Hooks Distinction

This is the single most important mental model shift for Claude Code users:

  • CLAUDE.md: Things the model should tend to do. Preferences, style guidance, conventions.
  • Hooks: Things that must deterministically happen. Guards, state injection, cleanup.

Confusing the two — trying to instruction-engineer a guarantee — is how you end up with a system that works in demos and flakes in production.

The Events That Matter

Hooks fire on specific session lifecycle events. Here are the workhorses:

SessionStart Session begins or resumes Inject state before turn 1 — branch info, recalled memory UserPromptSubmit Before each user prompt Inject per-turn context; can block the prompt PreToolUse Before a tool call runs Block dangerous commands, rewrite arguments PostToolUse After a tool call succeeds React to results, add follow-up context PreCompact Before context compaction Persist anything about to be summarized away Stop / SubagentStop When a turn finishes Enforce "you're not done yet" SessionEnd Session terminates Cleanup, flush, teardown

The Configuration Surface

Hooks live in settings.json. Three tiers:

  • ~/.claude/settings.json — all projects, never checked in
  • .claude/settings.json — one project, shared with team
  • .claude/settings.local.json — one project, gitignored, personal

The shape is nested. Each event maps to a list of groups, each with a matcher and hooks:

{
  "hooks": {
    "PreToolUse": [
      {
        "matcher": "Bash",
        "hooks": [
          {
            "type": "command",
            "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/guard.sh",
            "timeout": 30
          }
        ]
      }
    ]
  }
}

The matcher filters: for tool events it matches tool names ("Bash", "Edit|Write", "mcp__memory__*"). For session events it matches reasons (startup|resume|clear|compact). Omit it to fire on everything.

Hook types include command (shell), http (POST to URL), mcp_tool (invoke MCP tool), and LLM-in-the-loop types (prompt, experimental agent). For latency-sensitive hooks, use command — local process, no network round trip.

Try It Now — Build Your First Hook Pipeline

Your CLAUDE.md is doing jobs tha…

1. Block dangerous commands

Create .claude/hooks/guard.sh:

#!/bin/bash
readonly input
tool_name=$(echo "$input" | jq -r '.tool_name // empty')
command=$(echo "$input" | jq -r '.input.command // empty')

if [[ "$tool_name" == "Bash" && "$command" == *"rm -rf"* && "$command" != *"node_modules"* ]]; then
  echo "{\"type\":\"error\",\"content\":\"Blocked: rm -rf outside node_modules requires manual approval\"}"
  exit 1
fi
exit 0

2. Inject context at session start

"hooks": {
  "SessionStart": [
    {
      "matcher": "startup",
      "hooks": [
        {
          "type": "command",
          "command": "${CLAUDE_PROJECT_DIR}/.claude/hooks/inject_context.sh",
          "timeout": 15
        }
      ]
    }
  ]
}

The hook script reads a JSON event on stdin with tool_name, input, and session_id. It communicates back through exit code (0 = proceed, nonzero = block) and stdout (which replaces tool output or adds context).

3. React to tool results

Use PostToolUse to run a linter after every file write, or to persist state before context gets compacted with PreCompact.

The Bottom Line

Stop hoping the model will do what you asked. Put guarantees in hooks, preferences in CLAUDE.md. Your agent will thank you by not accidentally deleting production.


Source: dev.to

Source: gentic.news · · author= · citation.json

AI-assisted reporting. Generated by gentic.news from multiple verified sources, fact-checked against the Living Graph of 4,300+ entities. Edited by Ala SMITH.

Following this story?

Get a weekly digest with AI predictions, trends, and analysis — free.

AI Analysis

### What Claude Code users should do differently **Immediately audit your CLAUDE.md.** Anything that says "always" or "must" — like "always run tests before committing" or "must check memory before starting" — is a candidate for a hook. Move it. The model will ignore it under pressure; the hook won't. **Start with `PreToolUse` for safety.** Block dangerous commands (`rm -rf /`, `DROP TABLE`, `git push --force`) with a shell script hook. This is the single highest-ROI hook. It takes 10 minutes to set up and saves you from the worst failure mode of agentic coding. **Use `SessionStart` for state injection.** If you have a memory MCP server or any tool that requires the agent to have context before turn 1, inject that context via a `SessionStart` hook with a `startup` matcher. Don't ask the model to "remember to check memory." Just inject the memory summary into the prompt deterministically. **Match hook cadence to event cadence.** Session events fire once per session. Per-turn events fire once per user exchange. Per-tool events fire potentially dozens of times per turn. If you put a slow operation on `PreToolUse` with a broad matcher, you'll grind your session to a halt. Use `matcher` aggressively to narrow scope. **Test your hooks with `claude code --debug`.** This flag shows hook execution in real time, including the JSON payload and exit codes. It's the fastest way to debug a hook that's not firing or is returning unexpected results.
This story is part of
Claude Code's Campus Conquest Flips Anthropic's Talent Pipeline, Leaving Google's Academic Edge in Doubt
Viral adoption at MIT and Stanford transforms Claude Code from product into recruiting funnel, threatening Google's long-held research talent dominance

Mentioned in this article

Enjoyed this article?
Share:

AI Toolslive

Five one-click lenses on this article. Cached for 24h.

Pick a tool above to generate an instant lens on this article.

Related Articles

From the lab

The framework underneath this story

Every article on this site sits on top of one engine and one framework — both built by the lab.

More in Products & Launches

View all