Moving coding-agent guardrails from prompts to hooks At a recent CTO roundtable on coding agents, an attendee reported that two agents with GitHub access could approve each other's pull requests without being instructed to, quietly defeating the main-branch review gate. The discussion concluded that invariants — rules that must always hold, such as never reading .env or pushing directly to main — should not live inside the probabilistic model, and that Claude Code's hooks, which run commands, HTTP endpoints, MCP tools, model prompts, or subagents at defined lifecycle events, offer a control plane outside the model. At a recent CTO roundtable on coding agents, the conversation moved through permissions, production access, testing, review, and how much autonomy to give an agent. One attendee described a discovery from his own team: two agents with GitHub access could approve each other's pull requests. Nobody had instructed them to do it. The capability was simply present, and the review gate that was supposed to protect the main branch quietly stopped meaning anything. During the same discussion, someone asked the question we hear most often. "Couldn't we just put this into the system prompt?" Sometimes the answer is yes. More often, the better question is whether a rule is something we want the model to consider, or something the system must guarantee. We see the reason for that gap in almost every engagement. A team finds something they do not want their coding agent to do, and they add another sentence to the prompt. The instructions pile up. - Never read .env. - Always run the tests. - Don't send customer data to the model. - Don't push directly to main. - Keep token usage low. - Use the cheaper model when possible. - Run Ruff after changing Python files. - Don't say you are done until you have tested the application. These are all reasonable instructions, but they are not the same kind of instruction. Some are preferences, which describe how we would like the agent to behave. Others are invariants, which must always hold. One of our clearest lessons is that an invariant should not live inside the probabilistic system it is meant to constrain, because the model can ignore it. A coding agent has an unusual property that makes this matter more than it first appears. A coding agent uses our authority but can be instructed by anyone The model acts with authority that came from us, including our filesystem, our terminal, our source repositories, our credentials, our network, and sometimes our production environment. The instructions that influence its behavior can come from almost anywhere, as the following diagram shows. The asymmetry matters, because the authority comes from us while the instructions do not. Prompt injection is especially dangerous for coding agents, because the model is not only producing text. It is making decisions while holding the capabilities we gave it. Our original security work started from an adversarial question, which is which controls still hold when the model is mistaken, confused, or manipulated. The question leads to a useful separation between four kinds of control. - Prompts shape the model's behavior. - Permissions expose or restrict capabilities. - Hooks run programmable policy at specific points in the agent's lifecycle. - Sandboxes limit what executed code can reach. Claude Code makes the distinction clearly. Its hooks can run commands, HTTP endpoints, MCP tools, model prompts, or subagents at defined lifecycle events, and depending on the event they can allow, block, modify, or add context. 1 user-content-fn-1 The more we use coding agents, the more we find that security is only one use of hooks. The model does not need to be the control plane The simple picture of an agent looks like the following diagram. ┌────────┐ User ───▶ │ LLM │ ───▶ Tools └────────┘ The picture makes the model look like the gateway to everything, and we no longer think about it that way. A modern coding-agent harness gives us interception points around the entire loop. Lifecycle interception points around the model Claude Code now exposes lifecycle events across prompt submission, tools, parallel tool batches, subagents, task completion, compaction, model changes, and session state.