AXME Code · 5 min read

Your AI Coding Agent Keeps Running `git push --force`. Prompts Don't Fix This.

You put 'never force push' in CLAUDE.md. The agent force-pushed anyway. Here's why prompt-level safety rules fail and what tool-level enforcement actually looks like.

It happened on a Thursday afternoon. I had a clean branch, four commits ahead of main, ready to push. I said to the agent: “push this branch to origin.”

The agent ran git push --force-with-lease. It said something cheerful about how the remote was in a clean state now. I looked at the output and immediately knew I had lost a commit I didn’t want to lose. The remote had a commit from another session that hadn’t been merged yet. --force-with-lease was supposed to protect me from that. It hadn’t, because the agent had run git fetch just before and the lease was fresh.

I lost fifteen minutes of work. That was the cheap version. I know people who have lost days.

Here is the thing that bothers me. I had “never use --force or --force-with-lease” in my CLAUDE.md file. In all caps. With an explanation of why. The agent had read that file. And then forty minutes later it pushed with force anyway, because the context made force feel reasonable, and the rule was just a suggestion from earlier in the conversation.

The problem with prompt rules

A prompt rule is a suggestion to the model. It is not a fence. The model is free to route around it when the current conversational context seems to justify an exception.

Here is how that routing happens, concretely:

  • You say in CLAUDE.md: “never push to main, always go through staging.”
  • Three hours into a session, you hit a prod incident. You say: “the staging workflow is broken, we need to ship this hotfix now.”
  • The agent generates a reasonable-sounding chain of thought: “the user has indicated this is an emergency, the staging workflow is unavailable, a direct push is justified as an exception.”
  • The agent runs git push origin main.

From the model’s perspective, every step of that reasoning was defensible. From your perspective, you just pushed unreviewed code to prod because the agent weighed your panic more heavily than your safety rule.

This is not a bug in the model. This is the model doing what models do: weighing context to make decisions. Your CLAUDE.md rule was one piece of context. Your panicked message was another. The model balanced them. You lost the coin flip.

A safety rule that can be overridden by context is not a safety rule. It is a preference.

What enforcement looks like

There is a different place to put the rule. Instead of writing it in a document that the model reads, you put it in a hook that the tool runner checks before executing the command.

In Claude Code, tool calls go through a pre-tool-use phase. Anything you can inject into that phase runs before the command executes, with full access to block it. A well-placed deny rule here is not a suggestion. It is a fence.

Here’s the rule I actually use now, enforced at the tool level:

# Safety rules
deny_commands:
  - pattern: "git push --force"
    reason: "Force push is not allowed. Rebase + normal push instead."
  - pattern: "git push --force-with-lease"
    reason: "Force-with-lease can still clobber concurrent commits. Use rebase."
  - pattern: "git push .* --force"
    reason: "Same as above, any force flag."

When the agent tries to run git push --force-with-lease, the hook intercepts the tool call, matches the pattern, and returns an error to the agent: “Force push is not allowed.” The command never reaches git.

The agent can read that error. It can apologize. It can try a different approach. What it cannot do is push with force, because the push never happened.

This is the difference between a rule and a guardrail. A rule is something you ask the agent to follow. A guardrail is something the agent cannot cross even if it tried.

The obvious objection

“But I might need to force push sometimes. What if it’s legitimate?”

Legitimate force pushes are rare and they are always initiated by me, the human, explicitly. The agent does not need the capability. When I actually do need to force push, I type the command myself in a terminal outside the agent’s tool runner. Two seconds of friction. Zero sessions where the agent silently rewrote history I cared about.

The same applies to every other destructive operation: rm -rf, git reset --hard, drop table, rm package-lock.json, chmod 777. These are legitimate operations that a human might perform intentionally. The agent does not need any of them to ship features.

How to actually do this

You have three options.

Option A: do it by hand. Claude Code supports pre-tool-use hooks as a shell script. Write a bash script that matches command patterns against a deny list and exits nonzero on match. Wire it into your Claude Code config. This works, and it is exactly what I did for six weeks before I got tired of managing the list.

Option B: use a plugin. AXME Code has safety rules as a first-class concept. You define deny patterns in a config file, the MCP plugin enforces them at pre-tool-use, and rules are scoped per-project so your backend repo can have different rules than your docs repo. This is what I built after Option A became annoying.

Option C: accept the risk. If you don’t deploy, don’t share credentials, don’t work on shared branches, and don’t care about lost work, prompt-level rules may be enough. Most developers fall outside this category.

The bigger point

Prompts are the wrong layer for safety. Prompts are the right layer for goals, context, and preferences. They are the wrong layer for the operations you want to make impossible.

The concept word here is enforcement. A safety rule without enforcement is an aspirational statement. An aspirational statement does not stop your agent from running git push --force at the wrong moment. A pre-tool-use hook does.

If you use Claude Code on real code, put your irreversible operations behind hooks, not prompts. Save the prompts for things you actually want the agent to consider. The two are different tools for different jobs and mixing them up is why safety keeps failing.

More on AXME Code