Preventing Direct Commits to Main with Claude Hooks

I set up CLAUDE.md with (what I thought was) a very explicit rule: never commit directly to main. Claude acknowledged the instructions and committed directly to main anyway. I asked why. Claude said you're right, I shouldn't have done that — and then did it again and again. Clearly, more explicit instructions weren't going to fix this. That's when I reached for hooks — a way to inject a check at the actual moment of action rather than just at session start. First, I tried a "warn" hook that fires before every file edit. Better than CLAUDE.md alone, but Claude saw the warning and proceeded anyway. That's how warn hooks are designed — they inform, but they don't stop. What do you do when Claude independently decides to ignore its warnings? 🙃 So I upgraded to a "block" hook — a shell script that checks the current branch and blocks the edit if it's main. Claude hit it, created a branch, and moved on. Finally, something it couldn't override. There's a difference between a rule Claude has read and a mechanism it can't bypass. I don't know if I fully appreciated that until I built both. A warning it ignores is not a guardrail; it's just documentation. Where in your workflow are you relying on Claude reading the instructions — and where have you wired in something it can't route around?

Thanks for sharing this Taylor Keazirian. I really appreciate the spirit of sharing our learnings as we go. It’s also not lost on me that the vast majority of condescending “well actually” comments here are coming from men. I’m not surprised by it, but I’m continually disappointed in it. It highlights why we need to talk about these things openly. I think a lot of folks in the comments are missing the distinction between instruction and enforcement. Branch protection rules are for the repo, but you’re solving for local agent autonomy before a push even happens, which is a different beast. The control we think we have over AI guardrails is often an illusion until we can wire in a mechanism that it literally can’t bypass. This is a perfect example of why we can’t just rely on an LLM to follow rules in docs we give to it or even prompts. We need hard-coded guardrails for reliable, safe workflows. This kind of “leading through doing” is exactly how we’ll establish the new best practices for AI-assisted dev, so thanks again for sharing.

This doesn't seem to me like a Claude problem, but a process design problem. Any workflow that relies on actors (human or AI) remembering the rules will eventually fail. I've seen the same thing with humans merging to main with failed CI runs, bypassing reviews, or ignoring lint or static-code analysis warnings. My personal mantra is: "the Procedure should be enforced by itself." In practice, that means server-side guardrails like branch protection rules, required checks, restricted merge permissions; rather than instructions or client-side hooks that can be bypassed (for example your hook won't run if you commit via the GitHub UI). If breaking the rule is still possible, the procedure isn’t fully enforced yet.

Rules and guidelines in your CLAUDE.md (or AGENTS.md) that are lost is usually a sign that your file is too large. The model can't keep track of all your rules, or the rules are vague. This is an excellent guide on the topic: https://www.humanlayer.dev/blog/writing-a-good-claude-md The best practices from Anthropic are also useful: https://code.claude.com/docs/en/best-practices#write-an-effective-claude-md I give those two links to Claude and ask it to rewrite and split up any existing CLAUDE.md file.

I use AI tools for certain tasks in my developer flow. Analyze coding patterns for improvement, narrowing its scope to isolate bugs, having it track down complicated abstractions to rein them in for maintainability. Managing source control isn’t one of those. If I’m shipping generated code it needs to be reviewed and analyzed to make sure we aren’t introducing anti-patterns or bad solutions. I can’t personally let go of controlling what gets committed and manually managing source control. I know how easy it is to make mistakes that take hours to fix. In summary I don’t let agents make commits or handle anything git related outside of showing diffs.

If you put branch protection on main then re-run the session with the same prompt, does it backtrack when it discovers its attempt to commit to main fails?

Sometimes guardrails aren't enough, you'll need to setup branch protection with your git provider. Then when claud forgets it always goes, ah branch protection let me make a pull request and continues their merry way

Why can't you just set up permissions on GitHub to not allow committing to main?

Like
Reply

Looks like most of the comments here is focusing on the symptoms rather than the disease. Everyone here is treating the agent like a junior developer who needs a branch protection rule. It’s not. It’s an untrusted binary operating with your ambient OS permissions. Protecting the remote main branch is table stakes. But if you don't have a zero-trust execution sandbox, what stops a hallucinating or compromised agent from wiping your local .git directory, reading your SSH keys, or curling your local .env variables to an external server? You can't branch-protect your local file system. And you can’t patch every single security hole in such adhoc manner. Git workflows are for code quality. Execution boundaries are for security.

Like
Reply

Why not protect the branch on git repository?

It sounds like you don't have branch protection enforcement turned on. While yes, Claude shouldn't be doing that, you should also prevent it from being able to occur.

See more comments

To view or add a comment, sign in

Explore content categories