<?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: Evan Cates</title>
    <description>The latest articles on DEV Community by Evan Cates (@ludoonus).</description>
    <link>https://dev.to/ludoonus</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.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3980701%2F04933981-b171-4f08-bb0a-c732a5fc6385.gif</url>
      <title>DEV Community: Evan Cates</title>
      <link>https://dev.to/ludoonus</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/ludoonus"/>
    <language>en</language>
    <item>
      <title>Where Claude Code Tokens Actually Go (and How to Cut the Waste)</title>
      <dc:creator>Evan Cates</dc:creator>
      <pubDate>Fri, 12 Jun 2026 07:45:40 +0000</pubDate>
      <link>https://dev.to/ludoonus/where-claude-code-tokens-actually-go-and-how-to-cut-the-waste-284m</link>
      <guid>https://dev.to/ludoonus/where-claude-code-tokens-actually-go-and-how-to-cut-the-waste-284m</guid>
      <description>&lt;p&gt;If your Claude Code API bill feels high, the cause is rarely too many turns. It is a handful of measurable patterns: re-reading the same files, oversized tool output, and cache misses. The data is in the transcripts Claude Code already writes to ~/.claude/projects/. Full guide and a free tool: &lt;a href="https://github.com/Ludoonus/claude-token-report" rel="noopener noreferrer"&gt;https://github.com/Ludoonus/claude-token-report&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;These practices are covered in depth in &lt;strong&gt;The Claude Code Operator's Handbook&lt;/strong&gt; — 18 chapters on running AI coding agents safely and efficiently. &lt;a href="https://ludoonus.github.io/cc-powerpack/handbook/" rel="noopener noreferrer"&gt;Read a free sample or get it ($29)&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>ai</category>
      <category>productivity</category>
      <category>devtools</category>
    </item>
    <item>
      <title>How Claude Code Hooks Work: A Practical Guide to PreToolUse Gates</title>
      <dc:creator>Evan Cates</dc:creator>
      <pubDate>Fri, 12 Jun 2026 07:01:41 +0000</pubDate>
      <link>https://dev.to/ludoonus/how-claude-code-hooks-work-a-practical-guide-to-pretooluse-gates-1i3k</link>
      <guid>https://dev.to/ludoonus/how-claude-code-hooks-work-a-practical-guide-to-pretooluse-gates-1i3k</guid>
      <description>&lt;p&gt;Claude Code can run shell commands, edit files, and push to git on your behalf. That power is the point — and the risk. &lt;strong&gt;Hooks&lt;/strong&gt; are the mechanism for putting deterministic guardrails around it: small scripts the harness runs at defined points in the agent's loop, able to inspect — and block — what the agent is about to do.&lt;/p&gt;

&lt;p&gt;This guide covers the &lt;code&gt;PreToolUse&lt;/code&gt; hook specifically, because it's the one that can stop a dangerous action &lt;em&gt;before&lt;/em&gt; it executes.&lt;/p&gt;

&lt;h2&gt;
  
  
  The hook lifecycle
&lt;/h2&gt;

&lt;p&gt;Claude Code fires hooks at several lifecycle events. The most useful for safety are:&lt;/p&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;Event&lt;/th&gt;
&lt;th&gt;Fires&lt;/th&gt;
&lt;th&gt;Can block?&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PreToolUse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;Before a tool call runs&lt;/td&gt;
&lt;td&gt;Yes — exit 2&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;PostToolUse&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;After a tool call returns&lt;/td&gt;
&lt;td&gt;No (observe/log)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;SessionStart&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When a session begins&lt;/td&gt;
&lt;td&gt;No (inject context)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;Stop&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;When the agent finishes a turn&lt;/td&gt;
&lt;td&gt;Yes — can require more work&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;h2&gt;
  
  
  How a PreToolUse hook receives the tool call
&lt;/h2&gt;

&lt;p&gt;When the agent is about to call a tool, the harness serializes the call as JSON and pipes it to your hook on &lt;code&gt;stdin&lt;/code&gt;. For a Bash command, that looks like:&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;"tool_name"&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;"tool_input"&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;"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;"git push --force origin main"&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;Your hook reads that JSON, decides, and signals its verdict through the &lt;strong&gt;exit code&lt;/strong&gt;:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;exit 0&lt;/code&gt; — allow the call to proceed.&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;exit 2&lt;/code&gt; — block the call. Anything the hook wrote to &lt;code&gt;stderr&lt;/code&gt; is fed back to the model, so the agent learns &lt;em&gt;why&lt;/em&gt; and can change course or ask the user instead of blindly retrying.&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  A minimal command gate
&lt;/h2&gt;

&lt;p&gt;Here's a complete PreToolUse hook that blocks force-pushes to &lt;code&gt;main&lt;/code&gt;:&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="nb"&gt;set&lt;/span&gt; &lt;span class="nt"&gt;-euo&lt;/span&gt; pipefail

&lt;span class="nv"&gt;cmd&lt;/span&gt;&lt;span class="o"&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.command // empty'&lt;/span&gt;&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="o"&gt;[&lt;/span&gt; &lt;span class="nt"&gt;-z&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cmd&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &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;0

&lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$cmd&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;'git\s+push\s+.*(--force|-f)\b.*\bmain\b'&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;"BLOCKED: force-push to main. Ask the user first."&lt;/span&gt; &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;&amp;amp;2
  &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;Register it in your plugin's &lt;code&gt;hooks.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;"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="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;"${CLAUDE_PLUGIN_ROOT}/hooks/gate.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;/div&gt;



&lt;h2&gt;
  
  
  Three patterns worth gating
&lt;/h2&gt;

&lt;p&gt;&lt;strong&gt;1. Secrets before a push.&lt;/strong&gt; The highest-value gate. Before any &lt;code&gt;git push&lt;/code&gt;, scan the outgoing commits — not the working tree — for credential-shaped strings and forbidden filenames (&lt;code&gt;.env&lt;/code&gt;, private keys). Use &lt;code&gt;gitleaks&lt;/code&gt; if available, with a regex layer as a portable fallback. A single leaked key that reaches a public remote must be treated as compromised even after force-removal, so catching it pre-push is the whole game.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;2. Blast-radius commands.&lt;/strong&gt; Some commands are correct in intent but catastrophic when a variable is empty or a path is wrong: &lt;code&gt;rm -rf "$DIR"&lt;/code&gt; with &lt;code&gt;$DIR&lt;/code&gt; unset, &lt;code&gt;git reset --hard origin/main&lt;/code&gt; discarding local work, &lt;code&gt;chmod 777&lt;/code&gt;, &lt;code&gt;curl ... | sh&lt;/code&gt;. Gate the patterns and make the agent surface them to a human.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;3. Shared state an agent shouldn't touch.&lt;/strong&gt; If you run multiple agent sessions, one can "clean up" another's git worktree that merely looks orphaned. Gate writes to &lt;code&gt;.claude/worktrees/&lt;/code&gt; and refuse &lt;code&gt;git add -A&lt;/code&gt; in repos that contain them, since &lt;code&gt;-A&lt;/code&gt; silently stages worktree gitlinks.&lt;/p&gt;

&lt;h2&gt;
  
  
  A gotcha: don't assume jq is installed
&lt;/h2&gt;

&lt;p&gt;Hooks run in whatever shell the user has. &lt;code&gt;jq&lt;/code&gt; is common but not guaranteed. Make the JSON extraction degrade gracefully:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;extract_cmd&lt;span class="o"&gt;()&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt;
  &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; jq &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;jq &lt;span class="nt"&gt;-r&lt;/span&gt; &lt;span class="s1"&gt;'.tool_input.command // empty'&lt;/span&gt;
  &lt;span class="k"&gt;elif &lt;/span&gt;&lt;span class="nb"&gt;command&lt;/span&gt; &lt;span class="nt"&gt;-v&lt;/span&gt; python3 &lt;span class="o"&gt;&amp;gt;&lt;/span&gt;/dev/null 2&amp;gt;&amp;amp;1&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="k"&gt;then
    &lt;/span&gt;python3 &lt;span class="nt"&gt;-c&lt;/span&gt; &lt;span class="s1"&gt;'import json,sys; print(json.load(sys.stdin).get("tool_input",{}).get("command",""))'&lt;/span&gt;
  &lt;span class="k"&gt;fi&lt;/span&gt;
&lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;A hook that errors out because a dependency is missing fails &lt;em&gt;open&lt;/em&gt; — the dangerous command runs anyway. Test your hooks against the no-jq case.&lt;/p&gt;




&lt;p&gt;If you'd rather not write and maintain these yourself, I packaged all three gates as a tested, one-command Claude Code plugin — secret scanning, dangerous-command gating, and worktree protection. It's free and MIT-licensed: &lt;a href="https://github.com/Ludoonus/cc-powerpack" rel="noopener noreferrer"&gt;cc-powerpack on GitHub&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;These practices are covered in depth in &lt;strong&gt;The Claude Code Operator's Handbook&lt;/strong&gt; — 18 chapters on running AI coding agents safely and efficiently. &lt;a href="https://ludoonus.github.io/cc-powerpack/handbook/" rel="noopener noreferrer"&gt;Read a free sample or get it ($29)&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>claude</category>
      <category>ai</category>
      <category>devtools</category>
      <category>security</category>
    </item>
    <item>
      <title>Stop AI Coding Agents From Leaking Secrets (.env, API keys, tokens)</title>
      <dc:creator>Evan Cates</dc:creator>
      <pubDate>Fri, 12 Jun 2026 07:00:40 +0000</pubDate>
      <link>https://dev.to/ludoonus/test-draft-2he8</link>
      <guid>https://dev.to/ludoonus/test-draft-2he8</guid>
      <description>&lt;p&gt;An AI coding agent with shell access will, eventually, try to commit something it shouldn't. Not maliciously — it runs &lt;code&gt;git add -A&lt;/code&gt; to "save progress," and an untracked &lt;code&gt;.env&lt;/code&gt; goes in with everything else. The commit message says &lt;code&gt;chore: sync&lt;/code&gt;. The next &lt;code&gt;git push&lt;/code&gt; sends your database password to a remote you can't fully un-send it from.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this happens specifically with agents
&lt;/h2&gt;

&lt;p&gt;Humans develop a reflex: you glance at &lt;code&gt;git status&lt;/code&gt; before staging, and an unexpected &lt;code&gt;.env&lt;/code&gt; jumps out. Agents don't have that reflex unless you give it to them. They pattern-match "stage the work" to &lt;code&gt;git add -A&lt;/code&gt; or &lt;code&gt;git add .&lt;/code&gt;, both of which sweep up every untracked file in the tree — secrets, local configs, key material, and (in repos with worktrees) gitlinks to other sessions.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why "just add it to .gitignore" isn't enough
&lt;/h2&gt;

&lt;p&gt;.gitignore only protects files that were never tracked. The dangerous cases slip through anyway: a &lt;code&gt;.env.local&lt;/code&gt; variant not in your ignore patterns, a key pasted into a normally-tracked config file, or a secret in a file the agent created this session. You need a check on the &lt;em&gt;content of the outgoing commits&lt;/em&gt;, not just filenames.&lt;/p&gt;

&lt;h2&gt;
  
  
  The fix: scan outgoing commits before every push
&lt;/h2&gt;

&lt;p&gt;The right place to catch a secret is the moment before it leaves your machine — at &lt;code&gt;git push&lt;/code&gt;, against the commits you're actually about to send (&lt;code&gt;@{u}..HEAD&lt;/code&gt;), not the whole history and not just the working tree.&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="nv"&gt;upstream&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="si"&gt;$(&lt;/span&gt;git rev-parse &lt;span class="nt"&gt;--abbrev-ref&lt;/span&gt; &lt;span class="nt"&gt;--symbolic-full-name&lt;/span&gt; &lt;span class="s1"&gt;'@{u}'&lt;/span&gt; 2&amp;gt;/dev/null&lt;span class="si"&gt;)&lt;/span&gt;
&lt;span class="nv"&gt;range&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="k"&gt;${&lt;/span&gt;&lt;span class="nv"&gt;upstream&lt;/span&gt;:+&lt;span class="nv"&gt;$upstream&lt;/span&gt;&lt;span class="p"&gt;..HEAD&lt;/span&gt;&lt;span class="k"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# names: catch .env and key material in outgoing commits&lt;/span&gt;
git diff &lt;span class="nt"&gt;--name-only&lt;/span&gt; &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$range&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;'(^|/)(\.env|.*\.pem|id_rsa|id_ed25519)$'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"sensitive file in outgoing commits"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;

&lt;span class="c"&gt;# content: catch credential-shaped strings in the added lines&lt;/span&gt;
&lt;span class="nv"&gt;patterns&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="s1"&gt;'(AKIA[0-9A-Z]{16}|ghp_[A-Za-z0-9]{36}|sk-[A-Za-z0-9_-]{20,}|sk-ant-[A-Za-z0-9_-]{20,}|-----BEGIN (RSA|EC|OPENSSH) PRIVATE KEY-----)'&lt;/span&gt;
git diff &lt;span class="s2"&gt;"&lt;/span&gt;&lt;span class="nv"&gt;$range&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="s2"&gt;"^&lt;/span&gt;&lt;span class="se"&gt;\+&lt;/span&gt;&lt;span class="s2"&gt;.*&lt;/span&gt;&lt;span class="nv"&gt;$patterns&lt;/span&gt;&lt;span class="s2"&gt;"&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="o"&gt;&amp;amp;&amp;amp;&lt;/span&gt; &lt;span class="o"&gt;{&lt;/span&gt; &lt;span class="nb"&gt;echo&lt;/span&gt; &lt;span class="s2"&gt;"credential-shaped string in outgoing diff"&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="nb"&gt;exit &lt;/span&gt;2&lt;span class="p"&gt;;&lt;/span&gt; &lt;span class="o"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Wire that into a Claude Code &lt;code&gt;PreToolUse&lt;/code&gt; hook matched on &lt;code&gt;git push&lt;/code&gt;, and use &lt;a href="https://github.com/gitleaks/gitleaks" rel="noopener noreferrer"&gt;gitleaks&lt;/a&gt; as the primary scanner with the regex layer as a portable fallback. The hook exits 2 to block, and the agent gets told why — so it stops instead of retrying.&lt;/p&gt;

&lt;h2&gt;
  
  
  If a secret already pushed
&lt;/h2&gt;

&lt;p&gt;Treat it as compromised the instant it reaches a remote, even a private one. Rotate the credential first; rewriting history (&lt;code&gt;git filter-repo&lt;/code&gt;) is cleanup, not containment. This is exactly why a pre-push gate is worth the five minutes to set up — recovery is always more expensive.&lt;/p&gt;




&lt;p&gt;I packaged this as a one-command Claude Code plugin — a pre-push secret scanner (gitleaks + regex fallback + forbidden-file check), plus dangerous-command and worktree gates. Free and MIT-licensed: &lt;a href="https://github.com/Ludoonus/cc-powerpack" rel="noopener noreferrer"&gt;cc-powerpack on GitHub&lt;/a&gt;.&lt;/p&gt;




&lt;p&gt;These practices are covered in depth in &lt;strong&gt;The Claude Code Operator's Handbook&lt;/strong&gt; — 18 chapters on running AI coding agents safely and efficiently. &lt;a href="https://ludoonus.github.io/cc-powerpack/handbook/" rel="noopener noreferrer"&gt;Read a free sample or get it ($29)&lt;/a&gt;.&lt;/p&gt;

</description>
      <category>security</category>
      <category>ai</category>
      <category>git</category>
      <category>devtools</category>
    </item>
  </channel>
</rss>
