<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Michael Krisna</title>
    <description>The latest articles on DEV Community by Michael Krisna (@krisnamic).</description>
    <link>https://dev.to/krisnamic</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F1385161%2F97d85615-9a6e-4ae5-b7bf-352d19801c38.jpeg</url>
      <title>DEV Community: Michael Krisna</title>
      <link>https://dev.to/krisnamic</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/krisnamic"/>
    <language>en</language>
    <item>
      <title>Stop reviewing AI code. Start deleting it.</title>
      <dc:creator>Michael Krisna</dc:creator>
      <pubDate>Mon, 01 Jun 2026 10:36:17 +0000</pubDate>
      <link>https://dev.to/krisnamic/stop-reviewing-ai-code-start-deleting-it-o40</link>
      <guid>https://dev.to/krisnamic/stop-reviewing-ai-code-start-deleting-it-o40</guid>
      <description>&lt;p&gt;I asked Claude to fix a function. It rewrote three.&lt;/p&gt;

&lt;p&gt;I asked for a null check. It added five guard clauses, two of which protected against states the type system already prevents.&lt;/p&gt;

&lt;p&gt;I asked for one comment. It wrote a JSDoc block longer than the function it described.&lt;/p&gt;

&lt;p&gt;Every AI coding tool I've used does the same thing. The default move is to add. Most of the time, the right move is to delete.&lt;/p&gt;

&lt;h2&gt;
  
  
  TLDR
&lt;/h2&gt;

&lt;p&gt;Five things AI keeps adding that I delete on sight:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Comments that restate what the code does&lt;/li&gt;
&lt;li&gt;Defensive guards for states the type system already prevents&lt;/li&gt;
&lt;li&gt;Premature abstractions for one caller&lt;/li&gt;
&lt;li&gt;Try/catch around code that can't throw&lt;/li&gt;
&lt;li&gt;Verbose error messages that leak internals&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Five ways to make Claude Code stop adding them:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Run &lt;code&gt;/simplify&lt;/code&gt; after any AI write&lt;/li&gt;
&lt;li&gt;A CLAUDE.md anti-bloat rules block&lt;/li&gt;
&lt;li&gt;A "trimmer" subagent that proposes deletes&lt;/li&gt;
&lt;li&gt;Plan mode + an Architect agent that locks scope before code starts&lt;/li&gt;
&lt;li&gt;A PostToolUse hook that flags bloat metrics&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Code and configs below.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 5 things to delete
&lt;/h2&gt;

&lt;h3&gt;
  
  
  1. Comments that restate what the code does
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// Increment the counter by 1&lt;/span&gt;
&lt;span class="nx"&gt;counter&lt;/span&gt;&lt;span class="o"&gt;++&lt;/span&gt;

&lt;span class="c1"&gt;// Set the user object to null&lt;/span&gt;
&lt;span class="nx"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;null&lt;/span&gt;

&lt;span class="c1"&gt;// Loop through each item in the array and add it to the total&lt;/span&gt;
&lt;span class="k"&gt;for &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt; &lt;span class="k"&gt;of&lt;/span&gt; &lt;span class="nx"&gt;items&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;total&lt;/span&gt; &lt;span class="o"&gt;+=&lt;/span&gt; &lt;span class="nx"&gt;item&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Well-named identifiers do this job already. The comment adds reading load and rots first when the code changes.&lt;/p&gt;

&lt;p&gt;Comments earn their place by explaining &lt;em&gt;why&lt;/em&gt;: a hidden constraint, a subtle invariant, a workaround for a specific bug, behavior that would surprise the next reader. Restating the line above? Delete it.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Defensive guards for states the type system already prevents
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;greet&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="k"&gt;typeof&lt;/span&gt; &lt;span class="nx"&gt;name&lt;/span&gt; &lt;span class="o"&gt;!==&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;string&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="dl"&gt;''&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="s2"&gt;`Hello, &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The signature already says &lt;code&gt;name: string&lt;/code&gt;. The &lt;code&gt;typeof&lt;/code&gt; check protects against nothing. The &lt;code&gt;!name&lt;/code&gt; covers empty string, which is a valid input until the spec says otherwise.&lt;/p&gt;

&lt;p&gt;If untrusted data enters the system, validate at that boundary. Inside trusted code, trust the signatures.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Premature abstractions for one caller
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;getUserDisplayName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;User&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
  &lt;span class="nx"&gt;options&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nl"&gt;useFullName&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nl"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;?:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{}&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useFullName&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="kc"&gt;false&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;fallback&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Unknown&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;options&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;useFullName&lt;/span&gt;
    &lt;span class="p"&gt;?&lt;/span&gt; &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;firstName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;lastName&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;`&lt;/span&gt;
    &lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;username&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nx"&gt;fallback&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;

&lt;span class="c1"&gt;// Only caller in the codebase:&lt;/span&gt;
&lt;span class="nf"&gt;getUserDisplayName&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;currentUser&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The options bag is hypothetical optionality. One caller, one shape, one outcome. Inline it.&lt;/p&gt;

&lt;p&gt;The wrong abstraction costs more than three repeated lines. If a second caller appears with different needs, refactor then. Not now.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Try/catch around code that can't throw
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;a&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt; &lt;span class="nx"&gt;b&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;catch &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;console&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;addition failed&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;err&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="mi"&gt;0&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Addition does not throw. JSON parsing throws. Network calls throw. File reads throw. Add try/catch where the failure is real.&lt;/p&gt;

&lt;p&gt;The pattern shows up in subtler places too: try/catch around pure helpers, around getters on already-loaded data, around &lt;code&gt;JSON.stringify&lt;/code&gt; of objects that can't be circular. Every dead catch hides the next live one.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Verbose error messages that leak internals
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Error&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
  &lt;span class="s2"&gt;`Failed to fetch user data for userId &lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="nx"&gt;userId&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; from table users at `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="s2"&gt;`&lt;/span&gt;&lt;span class="p"&gt;${&lt;/span&gt;&lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;Date&lt;/span&gt;&lt;span class="p"&gt;().&lt;/span&gt;&lt;span class="nf"&gt;toISOString&lt;/span&gt;&lt;span class="p"&gt;()}&lt;/span&gt;&lt;span class="s2"&gt;. Database connection used pool size 10 with `&lt;/span&gt; &lt;span class="o"&gt;+&lt;/span&gt;
  &lt;span class="s2"&gt;`timeout 5000ms. SQL was: SELECT * FROM users WHERE id = $1`&lt;/span&gt;
&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two errors fighting each other. Too much detail to throw at the user (table name, SQL, internal pool stats). Too little signal to debug from (no trace, no upstream caller, no retry context).&lt;/p&gt;

&lt;p&gt;Throw the minimum the caller needs to recover. Log the diagnostic detail separately, structured, where the operator looks for it.&lt;/p&gt;




&lt;h2&gt;
  
  
  The 5 ways to stop AI from adding them
&lt;/h2&gt;

&lt;p&gt;Deleting is the cheap move. Preventing is the systemic one. Each layer below catches what the previous layer missed.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Run &lt;code&gt;/simplify&lt;/code&gt; after any AI write
&lt;/h3&gt;

&lt;p&gt;Claude Code ships with a built-in &lt;code&gt;simplify&lt;/code&gt; skill:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Review changed code for reuse, quality, and efficiency, then fix any issues found.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;After Claude writes or edits a file, type &lt;code&gt;/simplify&lt;/code&gt;. The skill reads the diff and proposes deletions and consolidations. One slash command, one round of trimming.&lt;/p&gt;

&lt;p&gt;This is reactive. It catches what already landed on disk. Useful as a tax, not as a strategy. The next four layers prevent the work from happening in the first place.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Drop an anti-bloat block into your CLAUDE.md
&lt;/h3&gt;

&lt;p&gt;Claude Code reads &lt;code&gt;CLAUDE.md&lt;/code&gt; at the start of every session. Put the rules in writing and most of the bloat never lands:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="gh"&gt;# Writing code&lt;/span&gt;
&lt;span class="p"&gt;
-&lt;/span&gt; Don't add features, refactor, or introduce abstractions beyond what the task requires. A bug fix doesn't need surrounding cleanup. Three similar lines is better than a premature abstraction.
&lt;span class="p"&gt;
-&lt;/span&gt; Don't add error handling, fallbacks, or validation for scenarios that can't happen. Trust internal code and framework guarantees. Only validate at system boundaries (user input, external APIs).
&lt;span class="p"&gt;
-&lt;/span&gt; Default to writing no comments. Only add one when the WHY is non-obvious: a hidden constraint, a subtle invariant, a workaround for a specific bug, behavior that would surprise a reader.
&lt;span class="p"&gt;
-&lt;/span&gt; Don't explain WHAT the code does. Well-named identifiers already do that. Don't reference the current task, fix, or callers; those belong in the PR description.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;These are paraphrased from Anthropic's own internal policy for Claude. The fact that Anthropic writes this down for its own model tells you what the default is.&lt;/p&gt;

&lt;p&gt;Drop the block into the project's &lt;code&gt;CLAUDE.md&lt;/code&gt; (or your user-scope one at &lt;code&gt;~/.claude/CLAUDE.md&lt;/code&gt;). Most of the patterns from the first half stop happening before they reach disk.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Build a "trimmer" subagent
&lt;/h3&gt;

&lt;p&gt;CLAUDE.md tells Claude what to do. A subagent with fresh context and the right scope makes sure it does it.&lt;/p&gt;

&lt;p&gt;Create &lt;code&gt;~/.claude/agents/trimmer.md&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;trimmer&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use after any code is written to find what should be deleted. Reviews the diff and proposes minimal cuts. Read-only, never edits. Invoke when the user says "trim this" or after Engineer finishes any non-trivial implementation.&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;haiku&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Grep, Glob, Bash&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

You find what to delete. You do not refactor, rewrite, or improve. You delete.

For each file changed in the current diff:
&lt;span class="p"&gt;
1.&lt;/span&gt; Read the full file, not just the diff.
&lt;span class="p"&gt;2.&lt;/span&gt; List every line you would cut, with file:line and a one-sentence reason.
&lt;span class="p"&gt;3.&lt;/span&gt; Group by reason: dead comment, redundant guard, premature abstraction, unused branch, leaky error.
&lt;span class="p"&gt;4.&lt;/span&gt; End with a verdict: total lines proposed for deletion, and whether the diff still works after.

You may not propose rewrites. You may only propose deletions. If you find code that should change rather than disappear, name it but do not touch it.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Run it with &lt;code&gt;Use the trimmer agent on the current diff&lt;/code&gt;. It returns a delete-only patch you approve in seconds.&lt;/p&gt;

&lt;p&gt;Haiku is the right tier here. The task is pattern matching, not deep reasoning, and Haiku writes terser output than the bigger models. Pick a heavier model and the trimmer starts explaining itself instead of cutting.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Plan mode + an Architect agent
&lt;/h3&gt;

&lt;p&gt;The cheapest bloat is the bloat that never gets written. Plan mode forces a written plan before any code happens. Pair it with a strict Architect subagent and you stop the problem at the source.&lt;/p&gt;

&lt;p&gt;The Architect:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight markdown"&gt;&lt;code&gt;&lt;span class="nn"&gt;---&lt;/span&gt;
&lt;span class="na"&gt;name&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;architect&lt;/span&gt;
&lt;span class="na"&gt;description&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Use before any non-trivial implementation. Designs the plan before code is written. Read-only.&lt;/span&gt;
&lt;span class="na"&gt;model&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;opus&lt;/span&gt;
&lt;span class="na"&gt;tools&lt;/span&gt;&lt;span class="pi"&gt;:&lt;/span&gt; &lt;span class="s"&gt;Read, Grep, Glob, WebSearch, WebFetch&lt;/span&gt;
&lt;span class="nn"&gt;---&lt;/span&gt;

You reject vague scope. "Add validation" is not a plan. "Reject empty strings in parseInput() at src/handlers.ts:42 and return HTTP 400 with body { error: 'name required' }" is a plan.

Every step in the plan names:
&lt;span class="p"&gt;-&lt;/span&gt; The file and approximate location
&lt;span class="p"&gt;-&lt;/span&gt; The exact change (signature, return shape, error message)
&lt;span class="p"&gt;-&lt;/span&gt; How it's verified (test name, manual check, type signature)

If the plan says "fix function X," the Engineer is not allowed to refactor function Y "while in there." Scope is fixed at planning time.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Plan mode plus a scope-strict Architect cuts the most expensive form of bloat: code Claude writes because nobody told it where to stop.&lt;/p&gt;

&lt;h3&gt;
  
  
  5. A PostToolUse hook for bloat metrics
&lt;/h3&gt;

&lt;p&gt;The first post in this series argued that hooks turn instructions into guarantees. Same idea applies here.&lt;/p&gt;

&lt;p&gt;A short hook that runs after every Write or Edit and flags bloat-adjacent metrics:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# ~/.claude/hooks/bloat-watch.sh&lt;/span&gt;
&lt;span class="c"&gt;# PostToolUse hook: warn Claude if the file looks bloated after writing.&lt;/span&gt;
&lt;span class="c"&gt;# Exits 2 with stderr when warnings fire so Claude sees them on the next turn.&lt;/span&gt;

&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.tool_input.file_path // ""'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0

&lt;span class="c"&gt;# Only check source files&lt;/span&gt;
&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;.ts|&lt;span class="k"&gt;*&lt;/span&gt;.tsx|&lt;span class="k"&gt;*&lt;/span&gt;.js|&lt;span class="k"&gt;*&lt;/span&gt;.jsx|&lt;span class="k"&gt;*&lt;/span&gt;.go|&lt;span class="k"&gt;*&lt;/span&gt;.py|&lt;span class="k"&gt;*&lt;/span&gt;.rs&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0 &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;

&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;wc&lt;/span&gt; &lt;span class="nt"&gt;-l&lt;/span&gt; &amp;lt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;tr&lt;/span&gt; &lt;span class="nt"&gt;-d&lt;/span&gt; &lt;span class="s1"&gt;' '&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="c"&gt;# grep -c always prints the count even when zero; the `|| true` swallows the exit 1 it returns on no-match.&lt;/span&gt;
&lt;span class="nv"&gt;comments&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-cE&lt;/span&gt; &lt;span class="s1"&gt;'^[[:space:]]*(//|#)'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;trycatch&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-cE&lt;/span&gt; &lt;span class="s1"&gt;'^[[:space:]]*(try|catch|except)'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;

&lt;span class="nv"&gt;warned&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;0

&lt;span class="c"&gt;# Comment density over 25% in files with more than 30 lines.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; total &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 30 &lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; comments &lt;span class="k"&gt;*&lt;/span&gt; 4 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; total &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Warning: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; has &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;comments&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt;/&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;total&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; lines as comments. Consider trimming the ones that restate the code."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nv"&gt;warned&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Many try/catch blocks in a single file.&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;((&lt;/span&gt; trycatch &lt;span class="o"&gt;&amp;gt;&lt;/span&gt; 6 &lt;span class="o"&gt;))&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"Warning: &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; has &lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;trycatch&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;&lt;span class="s2"&gt; try/catch blocks. Audit which failures are real and remove the rest."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &lt;span class="nv"&gt;warned&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;1
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="o"&gt;((&lt;/span&gt; warned &lt;span class="o"&gt;))&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="nb"&gt;exit &lt;/span&gt;0

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Register it under &lt;code&gt;PostToolUse: { matcher: "Write|Edit" }&lt;/code&gt; in &lt;code&gt;~/.claude/settings.json&lt;/code&gt;. The hook never blocks. It writes to stderr, which Claude reads on the next turn and adjusts.&lt;/p&gt;

&lt;p&gt;Tune the thresholds. Add more checks: new files created when the task didn't mention adding files, lines added in a single tool call, ratio of new exports to existing ones. The shape is the same: measure, surface, let Claude correct.&lt;/p&gt;




&lt;h2&gt;
  
  
  How the layers fit together
&lt;/h2&gt;

&lt;p&gt;A normal task with all five layers in play:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;You ask Claude to "add rate limiting to the /signals endpoint."&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan mode + Architect&lt;/strong&gt; produce a written plan that names the file, the exact middleware, the test to update.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CLAUDE.md anti-bloat rules&lt;/strong&gt; keep Engineer from adding a Redis abstraction "in case we need to swap stores later."&lt;/li&gt;
&lt;li&gt;Engineer writes the code.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PostToolUse bloat hook&lt;/strong&gt; logs that the new middleware file has a comment density of 30%, Claude trims on the next turn.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;&lt;code&gt;/simplify&lt;/code&gt;&lt;/strong&gt; catches one redundant guard the hook missed.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Trimmer subagent&lt;/strong&gt; finds three more lines to drop.&lt;/li&gt;
&lt;li&gt;Final diff is half the size it would have been.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Most days you use one or two layers, not all five. The point is that the discipline lives outside your head. You don't have to remember to delete. The setup reminds you.&lt;/p&gt;




&lt;h2&gt;
  
  
  What this changes
&lt;/h2&gt;

&lt;p&gt;AI-written code is a first draft. Treat it like one.&lt;/p&gt;

&lt;p&gt;Your bills come down. Your reviews get faster. The codebase stops growing for reasons no one can name a quarter later.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Delete more. Ship less.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Configs and scripts in this post are MIT-licensed and copy-pasteable. The first post in this series, on Claude Code hooks, is &lt;a href="https://dev.to/krisnamic/stop-telling-claude-code-rules-enforce-them-with-hooks-3po1"&gt;here&lt;/a&gt;.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>productivity</category>
      <category>automation</category>
    </item>
    <item>
      <title>Stop telling Claude Code rules. Enforce them with hooks.</title>
      <dc:creator>Michael Krisna</dc:creator>
      <pubDate>Sun, 24 May 2026 14:13:09 +0000</pubDate>
      <link>https://dev.to/krisnamic/stop-telling-claude-code-rules-enforce-them-with-hooks-3po1</link>
      <guid>https://dev.to/krisnamic/stop-telling-claude-code-rules-enforce-them-with-hooks-3po1</guid>
      <description>&lt;p&gt;I stopped writing rules in CLAUDE.md.&lt;/p&gt;

&lt;p&gt;CLAUDE.md is Claude Code's project memory file: where you stash conventions, build commands, and "always do X" rules that should apply to every session. Mine had four lines about Node versions.&lt;/p&gt;

&lt;p&gt;Claude followed them. Most of the time.&lt;/p&gt;

&lt;p&gt;The other times were the problem. Over a long task, Claude's attention drifts; a rule loaded at the start of the session may not influence what it does after 2 hrs. A subagent might spawn for a focused job without the project's CLAUDE.md in its context. A fresh worktree on a sibling repo wouldn't have the rule at all. Any of those, and &lt;code&gt;pnpm build&lt;/code&gt; would quietly run on whatever Node was on PATH (22, in my case, not 20). Build succeeds. Wrong runtime. No error until deploy.&lt;/p&gt;

&lt;p&gt;I tried the obvious fix: add the rule in more places. User-scope CLAUDE.md. System prompt. Every prompt. Adherence improved. The failure mode never went away. It just got rarer and harder to catch.&lt;/p&gt;

&lt;p&gt;That's when it clicked. Asking Claude to follow a rule is not the same as making sure it does. &lt;strong&gt;More reminders weren't going to close that gap.&lt;/strong&gt;&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;CLAUDE.md tells Claude what to do. A hook makes sure Claude does it.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;h2&gt;
  
  
  TL;DR
&lt;/h2&gt;

&lt;p&gt;Four hooks (~30 lines of bash each) I now run on every Claude Code session:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;#&lt;/th&gt;
&lt;th&gt;Hook&lt;/th&gt;
&lt;th&gt;What it enforces&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;1&lt;/td&gt;
&lt;td&gt;&lt;code&gt;nvm-guard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every Node command must use Node 20&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2&lt;/td&gt;
&lt;td&gt;&lt;code&gt;main-guard&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No &lt;code&gt;git push&lt;/code&gt; to main/master&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;3&lt;/td&gt;
&lt;td&gt;&lt;code&gt;secret-scan&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;No Stripe/AWS/GitHub keys in written files&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;4&lt;/td&gt;
&lt;td&gt;&lt;code&gt;auto-format&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Every file Claude edits gets formatted&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Code for all four below.&lt;/p&gt;

&lt;h2&gt;
  
  
  What hooks are
&lt;/h2&gt;

&lt;p&gt;Hooks are shell scripts Claude Code runs at fixed lifecycle points: &lt;strong&gt;before&lt;/strong&gt; a tool call (&lt;code&gt;PreToolUse&lt;/code&gt;), &lt;strong&gt;after&lt;/strong&gt; a tool call (&lt;code&gt;PostToolUse&lt;/code&gt;), on session end (&lt;code&gt;Stop&lt;/code&gt;).&lt;/p&gt;

&lt;p&gt;The flow:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Claude wants to run something
        ↓
Hook receives JSON of what's about to happen
        ↓
Hook decides:
   exit 0  →  allow
   exit 2  →  block + stderr message returns to Claude
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;When a hook blocks, Claude reads the stderr and &lt;strong&gt;retries with the fix on its own&lt;/strong&gt;. You don't have to intervene. Claude can't forget hooks. Claude can't be talked out of them. They just run.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hook 1: Force Node 20
&lt;/h2&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# ~/.claude/hooks/nvm-guard.sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;tool_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.tool_name // ""'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.tool_input.command // ""'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$tool_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;==&lt;/span&gt; &lt;span class="s2"&gt;"Bash"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0

&lt;span class="c"&gt;# Match node-based commands at a command boundary&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="o"&gt;!&lt;/span&gt; &lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="s1"&gt;'(^|[;&amp;amp;|(]|&amp;amp;&amp;amp;|\|\|)[[:space:]]*(pnpm|npm|yarn|bun|npx|node)([[:space:]]|$)'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="k"&gt;fi&lt;/span&gt;

&lt;span class="c"&gt;# Allow if nvm use is already in the same invocation&lt;/span&gt;
&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s1"&gt;'nvm[[:space:]]+use'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi
if &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="s1"&gt;'^[[:space:]]*nvm[[:space:]]'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then &lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;fi

&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2 &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Node-based command detected without `nvm use 20`.

Re-issue as a single Bash invocation:

  source ~/.nvm/nvm.sh &amp;amp;&amp;amp; nvm use 20 &amp;amp;&amp;amp; &amp;lt;your original command&amp;gt;
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The block-then-retry, live:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;● Bash(pnpm run dev)
  ⎿  PreToolUse:Bash hook blocked
     Re-issue as: source ~/.nvm/nvm.sh &amp;amp;&amp;amp; nvm use 20 &amp;amp;&amp;amp; &amp;lt;your command&amp;gt;

● Bash(source ~/.nvm/nvm.sh &amp;amp;&amp;amp; nvm use 20 &amp;amp;&amp;amp; pnpm run dev)
  ⎿  Now using node v20.20.2
     ▲ Next.js 15.0.0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Two seconds slower. Zero wrong-version build mysteries.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hook 2: Block pushes to main
&lt;/h2&gt;

&lt;p&gt;We've all watched Claude type &lt;code&gt;git push origin main&lt;/code&gt; and felt something cold pass through us. Easy to prevent.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# ~/.claude/hooks/main-guard.sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nb"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.tool_input.command // ""'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$command&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="s1"&gt;'git[[:space:]]+push.*[[:space:]](main|master)([[:space:]]|$|:)'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  if&lt;/span&gt; &lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;CLAUDE_ALLOW_MAIN_PUSH&lt;/span&gt;&lt;span class="k"&gt;:-}&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;!=&lt;/span&gt; &lt;span class="s2"&gt;"1"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2 &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Push to main/master blocked.
Create a feature branch and PR, or relaunch with CLAUDE_ALLOW_MAIN_PUSH=1.
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;    &lt;span class="nb"&gt;exit &lt;/span&gt;2
  &lt;span class="k"&gt;fi
fi
&lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;The escape-hatch env var matters. Block too cleanly and Claude invents creative workarounds. Document the escape, and it stops improvising.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hook 3: Refuse to write secrets
&lt;/h2&gt;

&lt;p&gt;Saved me twice. Once, Claude pasted a real &lt;code&gt;STRIPE_SECRET_KEY&lt;/code&gt; into a JSDoc example. Caught before it hit disk.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# ~/.claude/hooks/secret-scan.sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;input&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;tool_name&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.tool_name // ""'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="nv"&gt;content&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$input&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.tool_input.content // .tool_input.new_string // ""'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;

&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$tool_name&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt;~ ^&lt;span class="o"&gt;(&lt;/span&gt;Write|Edit&lt;span class="o"&gt;)&lt;/span&gt;&lt;span class="nv"&gt;$ &lt;/span&gt;&lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;printf&lt;/span&gt; &lt;span class="s1"&gt;'%s'&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$content&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; | &lt;span class="nb"&gt;grep&lt;/span&gt; &lt;span class="nt"&gt;-qE&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'sk_(live|test)_[A-Za-z0-9]{20,}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'AKIA[0-9A-Z]{16}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'ghp_[A-Za-z0-9]{36}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'xox[baprs]-[A-Za-z0-9-]{10,}'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
     &lt;span class="nt"&gt;-e&lt;/span&gt; &lt;span class="s1"&gt;'-----BEGIN [A-Z ]+ PRIVATE KEY-----'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
  &lt;/span&gt;&lt;span class="nb"&gt;cat&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2 &lt;span class="o"&gt;&amp;lt;&amp;lt;&lt;/span&gt;&lt;span class="sh"&gt;'&lt;/span&gt;&lt;span class="no"&gt;EOF&lt;/span&gt;&lt;span class="sh"&gt;'
Possible secret detected. Write blocked.
Use sk_test_fake_replace_me or move the real value to .env (gitignored).
&lt;/span&gt;&lt;span class="no"&gt;EOF
&lt;/span&gt;  &lt;span class="nb"&gt;exit &lt;/span&gt;2
&lt;span class="k"&gt;fi
&lt;/span&gt;&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Add patterns for whatever your stack uses: internal API formats, OpenAI keys, anything distinctive.&lt;/p&gt;




&lt;h2&gt;
  
  
  Hook 4: Format on every edit
&lt;/h2&gt;

&lt;p&gt;Silent &lt;code&gt;PostToolUse&lt;/code&gt;. Claude writes, the file gets formatted before the next read. No more whitespace-noise diffs.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="c"&gt;#!/usr/bin/env bash&lt;/span&gt;
&lt;span class="c"&gt;# ~/.claude/hooks/auto-format.sh&lt;/span&gt;
&lt;span class="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;file&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.tool_input.file_path // ""'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;span class="o"&gt;[[&lt;/span&gt; &lt;span class="nt"&gt;-n&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="nt"&gt;-f&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="o"&gt;]]&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;0

&lt;span class="k"&gt;case&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="k"&gt;in&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;.ts|&lt;span class="k"&gt;*&lt;/span&gt;.tsx|&lt;span class="k"&gt;*&lt;/span&gt;.js|&lt;span class="k"&gt;*&lt;/span&gt;.jsx|&lt;span class="k"&gt;*&lt;/span&gt;.json|&lt;span class="k"&gt;*&lt;/span&gt;.md|&lt;span class="k"&gt;*&lt;/span&gt;.css&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; prettier &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; prettier &lt;span class="nt"&gt;--write&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--log-level&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;silent &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;.go&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; gofmt   &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; gofmt &lt;span class="nt"&gt;-w&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;            &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;.py&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; ruff    &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; ruff format &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--quiet&lt;/span&gt; &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
  &lt;span class="k"&gt;*&lt;/span&gt;.rs&lt;span class="p"&gt;)&lt;/span&gt;  &lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; rustfmt &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; rustfmt &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$file&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="nt"&gt;--quiet&lt;/span&gt;     &lt;span class="o"&gt;||&lt;/span&gt; &lt;span class="nb"&gt;true&lt;/span&gt; &lt;span class="p"&gt;;;&lt;/span&gt;
&lt;span class="k"&gt;esac&lt;/span&gt;
&lt;span class="nb"&gt;exit &lt;/span&gt;0
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Missing formatter? &lt;code&gt;|| true&lt;/code&gt; makes it a silent no-op. Claude doesn't even know the format happened.&lt;/p&gt;




&lt;h2&gt;
  
  
  Install
&lt;/h2&gt;

&lt;ol&gt;
&lt;li&gt;Save each script to &lt;code&gt;~/.claude/hooks/&lt;/code&gt; and &lt;code&gt;chmod +x ~/.claude/hooks/*.sh&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;p&gt;Add to &lt;code&gt;~/.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PreToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Bash"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Users/YOU/.claude/hooks/nvm-guard.sh"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
          &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Users/YOU/.claude/hooks/main-guard.sh"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;},&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Write|Edit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Users/YOU/.claude/hooks/secret-scan.sh"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;],&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="nl"&gt;"PostToolUse"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"matcher"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"Write|Edit"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt;
        &lt;/span&gt;&lt;span class="nl"&gt;"hooks"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;[{&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"type"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="nl"&gt;"command"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="s2"&gt;"/Users/YOU/.claude/hooks/auto-format.sh"&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="p"&gt;}]&lt;/span&gt;&lt;span class="w"&gt;
      &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
    &lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;&lt;span class="w"&gt;
  &lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;span class="p"&gt;}&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/li&gt;
&lt;li&gt;&lt;p&gt;Start a new Claude session. Run &lt;code&gt;/hooks&lt;/code&gt; to confirm they're loaded.&lt;/p&gt;&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Three things to know:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;matcher&lt;/code&gt; is &lt;strong&gt;regex&lt;/strong&gt; on the tool name. &lt;code&gt;Bash&lt;/code&gt;, &lt;code&gt;Write|Edit&lt;/code&gt;, &lt;code&gt;.*&lt;/code&gt; all work.&lt;/li&gt;
&lt;li&gt;Paths must be &lt;strong&gt;absolute&lt;/strong&gt;. &lt;code&gt;~&lt;/code&gt; won't expand inside settings.json.&lt;/li&gt;
&lt;li&gt;Multiple hooks under one matcher run in order. First &lt;code&gt;exit 2&lt;/code&gt; blocks the rest.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Five more worth building
&lt;/h2&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Test gate&lt;/strong&gt;: &lt;code&gt;PostToolUse&lt;/code&gt; runs the relevant test on every file edit; exit 2 on red&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Diff cap&lt;/strong&gt;: &lt;code&gt;PreToolUse&lt;/code&gt; blocks any single Write/Edit over N lines (forces small, reviewable changes)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Dependency lock&lt;/strong&gt;: block &lt;code&gt;pnpm add &amp;lt;pkg&amp;gt;&lt;/code&gt; unless &lt;code&gt;&amp;lt;pkg&amp;gt;&lt;/code&gt; is on an allowlist&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Cost warn&lt;/strong&gt;: &lt;code&gt;Stop&lt;/code&gt; hook pings you if the session burned more than $X&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Forbidden flag guard&lt;/strong&gt;: block &lt;code&gt;--no-verify&lt;/code&gt;, &lt;code&gt;--force&lt;/code&gt;, &lt;code&gt;--dangerously-skip-permissions&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;20-30 lines of bash each. Each one removes a class of mistakes you stop having to think about.&lt;/p&gt;




&lt;p&gt;You wouldn't write "please don't drop the database" in a runbook and call it a control. You'd revoke the permission.&lt;/p&gt;

&lt;p&gt;Same thing here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Stop telling Claude rules. Start enforcing them.&lt;/strong&gt;&lt;/p&gt;




&lt;p&gt;&lt;em&gt;All four scripts are MIT-licensed and copy-pasteable as-is. Drop your hook #5 in the comments.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>claudecode</category>
      <category>ai</category>
      <category>productivity</category>
      <category>automation</category>
    </item>
  </channel>
</rss>
