AI agents · 2026-07-15

The case for approve-before-it-runs.

The AI coding world has quietly settled on a default answer to agent safety, and I think it is the wrong one for version control. The default is what I call contain-after. Give the agent real write access, then build walls around the blast radius: run it in a sandbox, protect the main branch, keep backups, add a revert button, review the pull request when it is done. If something goes wrong, you contain the damage after the fact. It is a reasonable default for a lot of computing, because most systems are roughly reversible. Git is not that kind of system, and I spent this year building a Git client around the opposite default. This post is the argument for it.

Version control punishes containment.

Contain-after is how we treat junior engineers, CI systems and most automation: give access, monitor, roll back mistakes. It works because a bad deploy gets rolled back and a bad row gets restored from backup, and the world returns, more or less, to the state before the mistake. Three properties of git break that model.

First, history is the product. In most systems, state is what matters and history is a log you can prune. In git, the history is the thing you are making. An agent that damages history has not corrupted a cache you can rebuild; it has damaged the artifact itself.

Second, the worst mistakes are not reversible by revert. A revert creates a new commit that undoes a change going forward. It does not remove the original commit from history. If an agent commits a credential file, the secret is in the repository's past, and everyone who fetched has a copy. You can rewrite history, but rewriting shared history is itself one of the most dangerous operations in git. People have already watched coding agents auto-commit secrets exactly this way. The containment story for that failure is "rotate the credential and hope", which is not containment, it is cleanup.

Third, mistakes propagate at fetch speed. A force-push to a shared branch is not contained the moment you notice it; it is contained when every clone that pulled it has been repaired. That window is minutes on an active team. So the honest question is not "how do I limit the damage an agent can do to my repository" but "why is the agent able to do damage that cannot be limited".

Reviewing the PR is not the answer either.

The standard rebuttal is that we already have a human gate: the pull request. Let the agent do whatever it wants on a branch, and review the result. PR review is a fine gate for the one thing it gates: merging a branch. It does nothing for everything else an agent does with git. An agent working on your machine resets, rebases, discards working-tree changes, force-pushes its own branch, and commits, long before a PR exists. The secret committed to a feature branch leaks the moment the branch is pushed, PR review or not. And a discarded local change never even reaches the review; it is simply gone.

There is also a provenance hole. A PR shows you the final diff. It does not record what the agent intended, what it tried, what a human actually approved, or whether the commits in the branch were ever seen by a person at all. When someone asks, six months from now, "did a human review this change before it entered history", the PR page answers a narrower question than it appears to.

Approve-before, as a capability rule.

The alternative I built is approve-before: the agent can never execute a write, only propose one. Not as a policy the agent is asked to follow, but as a capability it does not have. The distinction matters more with agents than it ever did with humans. Policy says "you should not force-push to main". Capability says "there is no tool in your hands that pushes". A model that hallucinates an argument, misreads an instruction, or gets prompt-injected by a malicious README cannot violate a capability boundary, because the dangerous call does not exist on its side of the wire. In FluxGit's MCP server there are 23 read-only tools and 11 write tools, and every one of the 11 is a proposal. There is no execute tool for an injection to reach. The full catalog is in the product walkthrough; the shape is what matters here.

A proposal is a small, honest contract. The agent must state a reason in plain language. The desktop app renders an approval card: the exact diff that would be applied, a risk classification, and the restore point that will be captured before anything runs. The human approves or rejects. On approval, the operation executes through the same guarded pipeline a manual click uses, so previews, preflight checks and restore points apply unchanged. On rejection, nothing happened. If the app is not running, the agent gets a clear error instead of a silent no-op, because a safety system that fails quietly trains everyone to distrust it.

The required reason turned out to be more than paperwork. An agent that half-understands its task writes a reason that half-explains it, and a vague reason next to a concrete diff is very easy to reject. The reason field is a window into the model's actual state, presented at exactly the moment you are deciding whether to trust it.

Review behavior, not lines.

An approval gate is only as strong as a human's ability to read what is in front of them, and agents produce diffs sized to defeat that. A model that moves a function and reformats around it generates forty lines of red and green for zero behavior change, and a reviewer drowning in churn stops reading.

So the approval card opens on a structural summary of what the change does, files and totals and behavior first, before any line of diff. Under it, each file renders as a tree-sitter structural diff (60+ languages): code that moved or was reformatted within a file stays quiet, and only the tokens that changed are highlighted inside their structure. The scope is honest, per-file with no cross-file move detection, and a file the parser cannot handle says so and shows a plain text diff rather than a fabricated summary. The point is not prettier diffs. It is keeping the human gate readable at agent speed, because a gate nobody reads is contain-after with extra steps.

Provenance: the chain has to be provable.

Approval solves the moment of execution. It does not, by itself, solve the question that arrives later: prove what happened. Logs are the usual answer, and ordinary logs are weak evidence. A plain text log proves nothing to anyone who did not watch it being written; anything that can append can also rewrite.

So the audit chain here is signed. Every MCP call can be appended to a JSONL audit log, and with signing enabled, each entry carries an Ed25519 signature over a canonical form of the event. One proposal threads a single identifier through every layer: the agent's stated intent, the human's approve or reject decision, and the resulting commit SHA when the operation runs. A small CLI, verify-audit, takes the log and a public key and reports exactly which entries verify, which fail, and which are unsigned.

That gives you a sentence you cannot honestly say under contain-after: this commit exists because that agent proposed it for this stated reason and this human approved it, and I can hand you the evidence. As agent-written code becomes a normal part of production history, I think that sentence stops being a nice-to-have. Someone will ask for it after an incident, an audit, or a supply-chain scare, and "the agent had write access and here are some logs" will not be an acceptable answer.

Honesty requires two footnotes. Signing is opt-in; an unsigned log is still useful but proves less, and the verifier counts unsigned entries separately rather than pretending. And a signed log proves what went through this pipeline; it cannot prove that nothing bypassed git entirely. Provenance systems are floors, not force fields.

The strongest objection: approval fatigue.

The best argument against approve-before is not philosophical, it is ergonomic. Ask a human to approve every operation and eventually they stop reading. Code review already shows us the decay curve: past a certain size and frequency, review becomes ritual. I take this seriously because it is the failure mode that would make everything above theater.

Two design responses so far. Proposals batch: an agent can submit a plan of up to ten operations that a human reviews and approves as one unit, with execution stopping at the first failure, so routine sequences cost one decision instead of ten. And the approval card is built to be readable in seconds: the diff, the risk, the restore point, the reason, nothing else.

Whether that is enough, I genuinely do not know yet. There is some number of proposals per hour at which any human stops reading diffs, and I have not found it empirically. What I do know is that the failure modes are not symmetric. A rubber-stamped approval flow degrades into contain-after, with the audit trail intact. Contain-after cannot degrade into anything; it is already the floor. If the gate weakens, you are no worse off than the current default, and the provenance chain still tells you what happened.

Speed was never the real objection.

The last objection is that this slows agents down, and it does: by the seconds it takes a human to read a diff. Against that, weigh the hours of unwinding a polluted history, the days of rotating a leaked credential, and the permanent discount applied to every repository whose history nobody can vouch for.

The industry is racing to make agents faster at writing changes. The actual bottleneck, for anything that matters, is how fast a human can trust a change. Approve-before-it-runs is a bet that the second problem is the one worth solving, and that the right time to catch an agent's worst mistake is the moment before it becomes permanent, not the cleanup after. I would rather approve one real diff than revert one imaginary disaster. That is the whole thesis.