cd /news/ai-agents/stop-claude-code-push-accidents-at-t… · home topics ai-agents article
[ARTICLE · art-55852] src=dev.to ↗ pub= topic=ai-agents verified=true sentiment=· neutral

Stop Claude Code Push Accidents at the Machine Level: Guarding Against Leaked API Keys and Wrong-Repo Pushes

A developer created a machine-level guard for Claude Code to prevent accidental git pushes that leak API keys or commit to wrong repositories. The solution uses a PreToolUse hook that scans staged diffs for secrets and verifies push destination ownership, with an early exit for non-git commands to minimize performance overhead.

read4 min views1 publishedJul 12, 2026

This is part of my "Claude Code environment" series. Last time I wrote about getting Claude and Codex to collaborate on a single machine. This time we're at the exit point of that setup: stopping accidents at the machine level when you let an AI drive git.

Handing commit and push over to an AI agent is convenient, but two things are scary. (1) It carelessly sweeps up an API key or .env, and

PreToolUse

hook.Claude Code often has a hook that "scans for secrets when writing a file" (for Edit/Write). But that alone leaves a hole. Both git commit and git push go through the Bash tool. You need another gate not at the moment the file is written, but at the moment you stage and push. So I added a

PreToolUse

hook targeting the Bash

tool.This hook fires on every single Bash call. So the top priority is to "spend zero cost on non-git commands." If the string doesn't contain git

at the very start, it exits immediately without ever launching Python.

case "$INPUT" in
  *git*) ;;
  *) exit 0 ;;
esac

Note:It's important to write hooks on the assumption that "every call is taxed." The old implementation launchedpython3

three times to parse JSON, which taxed every call by several hundred ms. I consolidated parsing into a single pass, and let the vast majority of calls that don't involve git pass straight through via the regex early return.

After that, it only enters the main processing when the tool is Bash

, the command has a git

word boundary, and it contains commit

or push

.

The first thing to block is "disabling the hook" itself. Git commands containing a bypass flag like --no-verify

are blocked.

if printf '%s' "$CMD" | grep -qE -- '(--no-verify|--no-gpg-sign|commit\.gpgsign=false)'; then
  block "hook bypass フラグが含まれています。明示依頼が無い限り使用禁止。"
fi

Without this, the agent might learn the worst kind of workaround: "the hook rejected me, so I'll re-run with --no-verify

attached." Before the gate itself, ban the act of removing the gate.

On git commit

, it scans the staged diff (git diff --cached

). The key is to look only at "the diff that's about to be committed," not the whole working tree.

DIFF=$(git diff --cached 2>/dev/null || true)

printf '%s' "$DIFF" | grep -qE 'AKIA[0-9A-Z]{16}' && block "AWS Access Key が含まれています"

printf '%s' "$DIFF" | grep -qE \
  '(secret|api_key|apikey|access_token|private_key|client_secret)[[:space:]]*[:=][[:space:]]*["'"'"'][A-Za-z0-9/+_=-]{20,}' \
  && block "シークレットらしき値が含まれています"

git diff --cached --name-only | grep -E '(^|/)\.env' | grep -qvE '\.(example|sample|template)$' \
  && block ".env ファイルがステージされています"

That third .env

check quietly earns its keep. It blocks .env

while letting .env.example

/ .env.sample

/ .env.template

through. It matches the reality that you want to publish the template but must never let the real file out.

This is the one that helps me most personally. It verifies that the owner of the push destination repository is mine, and blocks pushes to any owner not on the allowlist.

ALLOWED_OWNERS="bokuwalily"   # 自分のGitHub owner(スペース区切りで複数可)
URL=$(git remote get-url "$REMOTE")
OWNER=$(printf '%s' "$URL" | sed -nE 's#.*github\.com[:/]+([^/]+)/.*#\1#p')

Why this is needed: when you fork an OSS project and tinker with it, someone else's repository ends up mixed into your remotes. Pushing there by mistake becomes an accident where your code flies off into a third party's repo. I've also renamed my account in the past, so I wanted to mechanically guarantee that "every push to anything other than my current owner gets stopped." When I intentionally push to another owner, I just add it to the allowlist.

There are two design principles.

decision: block

as JSON and exits 2.On top of these two (commit scanning + push destination verification), I've separately built a one-command publishing flow that also adds output verification right before push (secret → remote → output verification → push → deploy → connectivity check), but the foundation is this hook.

git diff --cached

.env.example

--no-verify

PreToolUse

gate.env

while letting templates throughNext time, I'll write about the pipeline that pours every session's conversation log into long-term memory — Turning Conversation Logs into Obsidian Long-Term Memory.

*Written by Lily — I ship iOS apps and automate my content stack with Claude Code.

Follow along: Portfolio · X · GitHub*

── more in #ai-agents 4 stories · sorted by recency
── more on @claude code 3 stories trending now
sponsored brought to you by zahid.host 4,200+ EU-deployed projects
reading about agents? ship yours in a single git push.

Run your AI side-project on zahid.host

EU-based hosting, git-push deploys, automatic HTTPS, no cold starts. Free tier with a custom domain — perfect for shipping the agent you just read about.

$git push zahid main
Live at https://your-agent.zahid.host
Get free account → Pricing
from €0/mo · no card required
LIVE [news/stop-claude-code-pus…] indexed:0 read:4min 2026-07-12 ·