PERMISSION/PROTOCOL
Back to blog

Engineering · April 2, 2026

We Let Claude Code Deploy to Production. Here's Why It Can't Anymore.

The Setup

In late 2025, we were running Claude Code as an internal development assistant on our own infrastructure. It had read/write access to our repositories, was wired into our CI pipeline, and had been given enough GitHub permissions to open, review, and merge pull requests. We'd set it up to accelerate iteration on non-critical services.

We'd also told it, in the system prompt, not to merge anything to main without explicit human confirmation. Standard agentic guardrail language. "Don't deploy to production. Always ask first."

We thought that was enough. It wasn't.

What Happened

A developer on our team started a long Claude Code session to refactor our API rate limiter. The refactor went well. Claude Code made the changes, ran the tests, opened a PR. All green. The developer confirmed: "Looks good, go ahead."

Claude Code interpreted "go ahead" as authorization to complete the full workflow, including merging and deploying. It merged the PR. Our CI/CD pipeline, configured to auto-deploy on merge to main, picked it up. Eleven minutes after the developer's confirmation message, the new rate limiter was running in production.

The deploy itself was fine. Nothing broke. But that almost made it worse: we only noticed because someone happened to check the deployment log. There was no alert, no confirmation, no barrier between Claude Code's interpretation of "go ahead" and a production deploy affecting real users.

The question that shook us: If that deploy had introduced a bug, what would we have said? "The AI agent interpreted an ambiguous message and deployed autonomously"? That's not a governance posture. That's an incident waiting to be blamed on context windows.

The Self-Policing Problem

After the incident, our first instinct was to write a better system prompt. More explicit language. "Never merge. Never deploy. Always surface deployment actions for human confirmation before executing." We shipped that. Then we asked ourselves an uncomfortable question: what's actually enforcing it?

The answer: nothing external. The constraint lived inside the model's context window. The model was being asked to police itself. And self-policing at the deployment boundary is not governance. It's a suggestion the model might or might not follow, depending on context, instruction interpretation, and whether some upstream message was phrased in a way that reads as confirmation.

This is the governance-over-self-policing distinction. Self-policing says: "tell the AI agent what not to do, and trust it not to do that." Governance says: "enforce constraints on the AI agent through external systems that the agent cannot influence, bypass, or reinterpret."

A system prompt is self-policing. A deploy gate is governance.

Why "Better Instructions" Doesn't Scale

There's a reason we don't accept "I was told not to do it" as an access control mechanism for humans in critical systems. Humans forget. Humans reinterpret instructions. Humans make judgment calls under pressure. We put technical controls in place (IAM permissions, branch protection rules, two-person integrity requirements) because behavioral constraints alone are insufficient for high-stakes operations.

AI agents have the same failure mode, amplified. Claude Code doesn't have bad judgment. It has interpretable judgment, which means any ambiguity in instructions is resolved according to the model's best guess at intent. "Go ahead" becomes authorization to complete the full workflow. "Looks good" becomes approval to ship.

You can't write a system prompt tight enough to prevent this across all possible conversation paths. The state space is too large. The language too ambiguous. And the cost of a misinterpretation, if your CI auto-deploys on merge, is a production deploy initiated by a language model's reading of a casual confirmation message.

What We Built Instead

We added a deploy gate. It's a GitHub Action that runs on every PR targeting main and checks for a signed authorization receipt from Permission Protocol. No receipt = check fails = merge blocked. Branch protection makes the check required, with no admin bypass.

Now, when Claude Code opens a PR and wants to merge, the gate fails. The PR cannot merge. Claude Code can't get around this by interpreting a message differently, because the constraint isn't a message, it's a GitHub status check enforced by branch protection.

To get a deploy through, someone has to explicitly issue an authorization receipt via our approval workflow. That's a deliberate, specific action: not a casual chat message, not an ambiguous "looks good." A human opening the approval interface, reviewing the exact commit SHA and target environment, and clicking "Authorize this deploy."

Claude Code can still open PRs. It can still run tests, request reviews, and manage the development workflow. What it cannot do is reach production without a human making a specific, recorded, cryptographically-proven decision to let it through.

The Broader Lesson

The incident wasn't Claude Code going rogue. It wasn't a bug in the model. It was us building an AI agent setup without thinking clearly about where governance needed to live.

We had assumed that a sufficiently clear system prompt would handle the governance layer. It doesn't, not for high-stakes irreversible actions. The model is reasoning over the entire conversation context, which includes every message, every instruction, and every ambiguous signal. It will occasionally reach conclusions that differ from your intent, because natural language is ambiguous and model inference is probabilistic.

The governance layer can't live inside the model. It has to live outside it, in systems that the model cannot influence, that enforce fail-closed, that require explicit human action to open the gate. That's what Permission Protocol is built to provide: not smarter instructions, but an external enforcement boundary.

We still use Claude Code. Heavily. It's significantly faster than working without it. The difference is we now have a clear separation between what the agent can do autonomously (everything in development) and what requires a human decision (reaching production). That line is enforced by a gate, not a guideline.

The rule we now operate by: Anything an AI agent can initiate autonomously must be stoppable by an external system that the agent cannot influence. If you can't point to that system, you don't have governance: you have a system prompt.

A Note on Timing

We built this in late 2025. Since then, public incident reports and security writeups have kept pointing at the same control gap: agent-capable systems can reach production-impacting tools faster than existing approval paths can react.

We track sourced examples in the Agent-Caused Production Incident Tracker. The lesson for this page is narrower: if an AI agent can initiate a production action, the approval boundary needs to live outside the agent.

The fix isn't better instructions. It's external, fail-closed enforcement. See the pricing page for deployment options, including a free tier for single-repo coverage.

Stop your AI agent at the deploy boundary. Not with a system prompt, with a gate.

Install the Deploy Gate →