<?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: Taehyeon Jeon</title>
    <description>The latest articles on DEV Community by Taehyeon Jeon (@taehyeon_jeon_5618e0c8332).</description>
    <link>https://dev.to/taehyeon_jeon_5618e0c8332</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%2F4047715%2F20bdf51a-d916-48ba-a811-f994f1f6ed54.png</url>
      <title>DEV Community: Taehyeon Jeon</title>
      <link>https://dev.to/taehyeon_jeon_5618e0c8332</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/taehyeon_jeon_5618e0c8332"/>
    <language>en</language>
    <item>
      <title>CLAUDE.md can't stop Claude Code from editing your .env. A hook can.</title>
      <dc:creator>Taehyeon Jeon</dc:creator>
      <pubDate>Sun, 26 Jul 2026 16:28:11 +0000</pubDate>
      <link>https://dev.to/taehyeon_jeon_5618e0c8332/claudemd-cant-stop-claude-code-from-editing-your-env-a-hook-can-2k7g</link>
      <guid>https://dev.to/taehyeon_jeon_5618e0c8332/claudemd-cant-stop-claude-code-from-editing-your-env-a-hook-can-2k7g</guid>
      <description>&lt;p&gt;You wrote it in &lt;code&gt;CLAUDE.md&lt;/code&gt;. Right at the top, in bold:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Do not modify files I did not ask you to modify.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;It worked. For a while.&lt;/p&gt;

&lt;p&gt;Then the session got long, you were forty messages into a refactor, and it edited &lt;code&gt;.env&lt;/code&gt; anyway. Or &lt;code&gt;migrations/&lt;/code&gt;. Or the config file you spent an hour getting right.&lt;/p&gt;

&lt;p&gt;You add the rule again, louder. &lt;code&gt;NEVER&lt;/code&gt;. &lt;code&gt;CRITICAL&lt;/code&gt;. Three exclamation marks. It holds for a bit, and then it doesn't.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why louder doesn't work
&lt;/h2&gt;

&lt;p&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt; is text in the prompt. That's all it is.&lt;/p&gt;

&lt;p&gt;Everything else in that window is also text in the prompt — the abandoned approach from twenty minutes ago, the error you pasted, the file you asked it to read. As the conversation grows, your rule is one line competing with thousands, and it doesn't have special standing just because you wrote it in caps.&lt;/p&gt;

&lt;p&gt;So the model isn't disobeying. It's weighing, and your line lost.&lt;/p&gt;

&lt;p&gt;That means no wording fixes this. It's not a prompting problem. &lt;strong&gt;You can't solve an enforcement problem with a request.&lt;/strong&gt;&lt;/p&gt;

&lt;h2&gt;
  
  
  The hook runs before the tool call
&lt;/h2&gt;

&lt;p&gt;Claude Code fires a &lt;code&gt;PreToolUse&lt;/code&gt; hook before it executes a tool. The hook gets the tool name and its arguments on stdin. If the hook exits with code &lt;code&gt;2&lt;/code&gt;, the tool call is cancelled and stderr goes back to the model.&lt;/p&gt;

&lt;p&gt;The model doesn't decide. Your code does.&lt;/p&gt;

&lt;p&gt;Here's a working version. No dependencies.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# .claude/guard.py
&lt;/span&gt;&lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;sys&lt;/span&gt;
&lt;span class="kn"&gt;from&lt;/span&gt; &lt;span class="n"&gt;pathlib&lt;/span&gt; &lt;span class="kn"&gt;import&lt;/span&gt; &lt;span class="n"&gt;Path&lt;/span&gt;

&lt;span class="n"&gt;PROTECTED&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;.env&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;credentials.json&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt;

&lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_name&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Edit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Write&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;MultiEdit&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;}:&lt;/span&gt;
    &lt;span class="n"&gt;path&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;tool_input&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="ow"&gt;or&lt;/span&gt; &lt;span class="p"&gt;{}).&lt;/span&gt;&lt;span class="nf"&gt;get&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;file_path&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="sh"&gt;""&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nc"&gt;Path&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;path&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;name&lt;/span&gt; &lt;span class="ow"&gt;in&lt;/span&gt; &lt;span class="n"&gt;PROTECTED&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stderr&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;write&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Blocked: &lt;/span&gt;&lt;span class="si"&gt;{&lt;/span&gt;&lt;span class="n"&gt;name&lt;/span&gt;&lt;span class="si"&gt;}&lt;/span&gt;&lt;span class="s"&gt; is protected. Do not try another path. &lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;
            &lt;span class="sa"&gt;f&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;Tell the user and let them edit it themselves.&lt;/span&gt;&lt;span class="se"&gt;\n&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
        &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;2&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&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;Wire it up in &lt;code&gt;.claude/settings.json&lt;/code&gt;:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&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;"Edit|Write|MultiEdit"&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;"python3 .claude/guard.py"&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;/div&gt;



&lt;p&gt;Restart Claude Code. Test it without waiting for an accident:&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="nb"&gt;echo&lt;/span&gt; &lt;span class="s1"&gt;'{"tool_name":"Edit","tool_input":{"file_path":".env"}}'&lt;/span&gt; | python3 .claude/guard.py
&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"exit=&lt;/span&gt;&lt;span class="nv"&gt;$?&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;exit=2&lt;/code&gt; means it's live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Three things that are easy to get wrong
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Write the stderr message for the model, not for you.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;code&gt;"Permission denied"&lt;/code&gt; gets you a model that tries &lt;code&gt;Bash&lt;/code&gt; with &lt;code&gt;cat &amp;gt; .env&lt;/code&gt; instead. It's being helpful — it thinks the tool failed, not that the action is forbidden.&lt;/p&gt;

&lt;p&gt;Tell it what to do next:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Blocked: .env is protected. Do not try another path.
Tell the user and let them edit it themselves.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;That single sentence is the difference between a guard and a game of whack-a-mole.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Fail open, not closed.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;If your hook throws, every write in the project dies and you'll spend an hour thinking Claude Code is broken.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="k"&gt;try&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;json&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;load&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;stdin&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="k"&gt;except&lt;/span&gt; &lt;span class="nb"&gt;Exception&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
    &lt;span class="n"&gt;sys&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;exit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="mi"&gt;0&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;   &lt;span class="c1"&gt;# a broken guard must not become a broken editor
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A guard that blocks everything when it breaks is worse than no guard.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Protect the guard itself.&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Add &lt;code&gt;.claude/guard.py&lt;/code&gt; and &lt;code&gt;.claude/settings.json&lt;/code&gt; to your protected list. A model that's stuck will absolutely try to "fix" the thing blocking it, and it will be very reasonable about it.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this doesn't cover
&lt;/h2&gt;

&lt;p&gt;It doesn't touch &lt;code&gt;Bash&lt;/code&gt;. &lt;code&gt;rm -rf&lt;/code&gt; sails straight past. If that matters, add a &lt;code&gt;Bash&lt;/code&gt; matcher and inspect the command string — but be aware you're now writing a shell parser, and that's a bigger job than it looks.&lt;/p&gt;

&lt;p&gt;It's a guardrail, not a permission system. It stops accidents, not a determined adversary.&lt;/p&gt;

&lt;h2&gt;
  
  
  The general shape
&lt;/h2&gt;

&lt;p&gt;Once this clicks, the split is obvious:&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;Where it lives&lt;/th&gt;
&lt;th&gt;When it loses&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;CLAUDE.md&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;in the prompt&lt;/td&gt;
&lt;td&gt;context grows, priority drops&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;hook&lt;/td&gt;
&lt;td&gt;on your machine&lt;/td&gt;
&lt;td&gt;never — it isn't the model's decision&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;Use both. &lt;code&gt;CLAUDE.md&lt;/code&gt; explains &lt;em&gt;why&lt;/em&gt; so the model cooperates. The hook makes &lt;em&gt;can't&lt;/em&gt; actually mean can't.&lt;/p&gt;

&lt;p&gt;Anything you'd be genuinely upset to lose belongs on the second list.&lt;/p&gt;




&lt;p&gt;I put a fuller version on GitHub — glob patterns, a &lt;code&gt;protected.txt&lt;/code&gt; you edit instead of the script, every path in a &lt;code&gt;MultiEdit&lt;/code&gt; batch, and the fail-open handling:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;&lt;a href="https://github.com/avenna01-ceo/claude-code-survival-kr/tree/main/guard" rel="noopener noreferrer"&gt;github.com/avenna01-ceo/claude-code-survival-kr&lt;/a&gt;&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;The repo also has the rules and prompts that go in &lt;code&gt;CLAUDE.md&lt;/code&gt; itself. Free, MIT.&lt;/p&gt;

&lt;p&gt;If you've got a pattern that's saved you, I'd genuinely like to see it — the useful ones all came from someone getting burned first.&lt;/p&gt;

</description>
      <category>ai</category>
      <category>python</category>
      <category>productivity</category>
      <category>tutorial</category>
    </item>
  </channel>
</rss>
