Which coding agent hooks fail open, and which fail closed?
Claude Code and GitHub Copilot HTTP hooks fail open: when the hook times out, can't connect, or gets an error response, the agent runs the action anyway. The one exception is Claude Code's PreModelSwitch, where a timeout blocks the switch. OpenAI Codex and Cursor, installed through the Arcjet command wrapper, fail closed and deny the action instead. Fail-open is a design choice each vendor made for its hooks, not a property of the policy service.
A coding agent hook is a call that the agent's own process makes at a lifecycle event, such as before a tool runs. Claude Code and Copilot send that call as an HTTP request. Codex and Cursor have no HTTP hook type, so the Arcjet install for them is a script that posts the payload and turns any failure into a denial.
Arcjet gives you strong enforcement on both kinds of agent. On Claude Code and Copilot, Arcjet evaluates policies at the edge in over 300 data centers, so decisions return well inside the hook timeout. On Codex and Cursor, the Arcjet wrapper fails closed, so the control stays in force through an outage. Arcjet also records OpenTelemetry and Claude Compliance API activity beside hook decisions, so you can find any session that ran without a decision.
This article covers each behavior, what it means for how strong the control is, and how to find the sessions that ran without a decision. For the wider picture, see How do you secure AI coding agents?.
How does each agent behave when a hook fails?
The following table summarizes the behavior described in the Arcjet coding agents documentation.
| Agent | Hook type | On timeout, network error, or non-2xx | On a wrong or missing key |
|---|---|---|---|
| Claude Code | HTTP | The action proceeds. On | A 401 doesn't block PreToolUse. The tool call proceeds. |
| GitHub Copilot | HTTP | The action proceeds. | The action proceeds. |
| OpenAI Codex | Command wrapper | The wrapper prints Codex's denial shape and exits 2, which Codex treats
as a deny on | Every prompt and tool call is denied. |
| Cursor | Command wrapper | The wrapper prints Cursor's denial shape and exits 2. With | Every prompt and tool call is denied. |
Why do Claude Code and Copilot fail open?
Claude Code and Copilot run HTTP hooks natively: the agent sends the request itself and reads the JSON response. Both vendors designed their hooks to treat a failed request as no answer and continue, except on Claude Code's PreModelSwitch.
The Arcjet templates set a 5-second timeout on each entry. For Claude Code, that's "timeout": 5, and for Copilot, "timeoutSec": 5. The agent treats a response slower than that like any other failure.
Claude Code's PreModelSwitch is the reverse. A timeout blocks the model switch, and the default timeout is 30 seconds. The Arcjet install sets timeout: 30 on that entry. A slow response there can block a legitimate switch, and Arcjet's edge evaluation keeps the answer fast on this event as on every other.
How do Codex and Cursor fail closed?
Codex and Cursor run hooks as commands, which lets Arcjet choose the failure behavior. The Arcjet install is a short shell script, with a PowerShell equivalent for Windows. It reads the hook payload from standard input, posts it to Arcjet with curl, and prints the response. The following excerpt from the Codex wrapper shows the failure handling:
code=$(curl -sS --max-time 5 -o "$tmp" -w '%{http_code}' -X POST \ "https://decide.arcjet.com/v1/agent-hooks/${vendor}?event=${event}" \ -H "Authorization: Bearer ${ARCJET_KEY}" \ -H "Content-Type: application/json" \ -H "X-Arcjet-Principal: ${principal}" \ --data-binary @-) || denycase "$code" in 2??) ;; *) deny ;;esacThe deny function prints the vendor's denial shape for that event and exits 2. A transport failure, a non-2xx status, or a 2xx body that isn't JSON all reach it.
Cursor built its hooks to fail open by default: a crash, a timeout, or invalid JSON lets the action through. The Arcjet template for Cursor sets failClosed: true on the two enforcement entries, preToolUse and beforeSubmitPrompt, so those cases deny as well. The recorded events, such as postToolUse and stop, don't set it.
A rollout mistake therefore shows up immediately rather than as a silent gap. A wrong or missing ARCJET_KEY denies every Codex and Cursor prompt and tool call, so test the key on one machine before you push the configuration to a fleet.
What does fail-open mean for enforcement strength?
On an agent that fails open, enforcement is only as strong as the hook endpoint's availability and latency. Any policy service used this way needs to answer well within the timeout and stay available, because on Claude Code and Copilot an outage or a slow response lets tool calls through without a decision.
Arcjet evaluates coding agent policies at the edge in over 300 data centers, so the added latency stays small. The agents run matching hooks for an event in parallel, so the Arcjet entries add one request of latency, typically a few tens of milliseconds, well inside the 5-second timeout.
Failing open applies to the transport, not to the policy. When Arcjet answers, a policy that can't reach a verdict denies. For example, a destination threat lookup that can't be completed fails the policy closed, and a live detector rule whose input is absent on that event denies every call. For that reason, attach a detector policy only to the Execute on option whose events fill its input.
Is fail-open or fail-closed better for coding agent hooks?
The Claude Code and Copilot HTTP hooks fail open as the vendors built them, and the Arcjet wrapper for Codex and Cursor fails closed by design. The trade-off affects how you roll out:
- Fail-open keeps developers working through an outage or a misconfiguration, and leaves a window where actions run without a decision.
- Fail-closed keeps the control in force through an outage, and stops developers working when the hook can't reach Arcjet or the key is wrong.
On every agent, publish new policy rules in dry run first. Dry run records what a rule would have denied without denying anything. Review those calls in the site's Activity in the Arcjet Console, then set the rule live. For how the per-agent installs differ, see the guides for Claude Code, GitHub Copilot, Cursor, and OpenAI Codex.
How do you find sessions that ran without a hook decision?
Compare hook decisions against a second, observe-only source. Claude Code exports OpenTelemetry logs to Arcjet, and Arcjet polls the Claude Compliance API for your organization's activity. Both record sessions whether or not a hook fired, and Arcjet shows them in the same Arcjet Console views as hook decisions. Decisions also export to Datadog, Splunk, SentinelOne, Panther, and Amazon S3 (Enterprise plan), so you can alert on the gap in the tools your security team already uses.
Send that backstop to a separate Arcjet site from the hooks. If both paths feed the same site, each session is ingested twice under different identifiers, with no way to correlate the copies. A session that appears in the backstop with no hook decision is one to investigate. It can mean a hook that failed open, a hook that was removed, or a client the hooks never reached. For setup, see Observe agent activity.
Does a hook apply to a person or a client?
A hook applies to one client configuration, not one person. Hooks go wherever the configuration that carries them goes, so install Arcjet through managed settings or MDM, where developers can't remove it without administrator access. The following cases sit outside a client's hooks, with what to pair Arcjet with where one applies:
- Anyone who can edit a repository can delete its repository-level hook file. Use managed configuration for anything beyond a pilot.
- Claude Code skips the server-managed settings fetch for a session that uses a third-party model provider, such as
CLAUDE_CODE_USE_BEDROCK, or pointsANTHROPIC_BASE_URLelsewhere. Endpoint-managed settings still apply there. - A developer who calls a model API from another tool is outside every hook.
- A personal Claude, ChatGPT, Copilot, or Cursor login on a corporate laptop reaches neither hooks nor the Claude Compliance API. For network and device controls, see how to block personal AI accounts on work laptops.
The identity in a hook is also asserted, not authenticated. The X-Arcjet-Principal header carries a value such as $USER from the developer's environment, and Arcjet treats it as untrusted metadata. Use it to attribute sessions, not to authorize them.
Two other limits apply regardless of transport. A recorded event, such as post-tool-use, can never block, because the tool has already run. An instruction file such as AGENTS.md isn't a fallback either, because the model can ignore it. For more information, see AGENTS.md is not a security control.
Where does Arcjet fit when hooks fail open?
With Arcjet, you get strong enforcement on every supported agent, whichever failure behavior its vendor chose. Arcjet gives you the following capabilities:
- Edge evaluation: on Claude Code and Copilot, Arcjet answers from over 300 data centers, typically in a few tens of milliseconds, well inside the hook timeout.
- A fail-closed wrapper: on Codex and Cursor, the Arcjet wrapper denies on any transport failure, so an outage doesn't open a gap.
- Reconciliation: OpenTelemetry and Claude Compliance API activity sit beside hook decisions in the Arcjet Console, so a session that ran without a decision stands out.
- One policy: the same Rego policies apply to all four agents, so you don't maintain per-vendor rules.
To start, install the hooks on one machine, confirm that a harmless tool call appears in Activity, and publish the coding-agent.destructive-command starter policy in dry run before you roll the configuration out to a fleet.
Frequently asked questions
Do coding agent hooks fail open?
Claude Code and GitHub Copilot HTTP hooks fail open, so a timeout, network error, or error response lets the action proceed, except on Claude Code's PreModelSwitch. OpenAI Codex and Cursor, installed through the Arcjet command wrapper, fail closed and deny the action instead.
What happens to Claude Code if the hook service is down?
Claude Code HTTP hooks fail open on a timeout, network error, or non-2xx response, so the tool call or prompt proceeds. The exception is PreModelSwitch, where a timeout blocks the model switch.
Do Cursor and Codex hooks fail closed?
With the Arcjet command wrapper, yes. The wrapper prints the vendor's denial shape and exits 2 on any transport failure, non-2xx, or non-JSON response, and Cursor is configured with failClosed: true. A wrong key denies every Codex and Cursor prompt and tool call.
Does a coding agent hook add latency?
Coding agents run matching hooks for an event in parallel, so the Arcjet entries add one request of latency, typically a few tens of milliseconds. Arcjet evaluates policies at the edge in over 300 data centers, so its answer returns well inside the 5-second timeout that the install templates set.
How do I find coding agent sessions that ran without a policy decision?
Send OpenTelemetry or Claude Compliance API activity to a separate Arcjet site from the hooks. A session in that backstop with no hook decision is one the hooks didn't reach or that failed open.
AI runtime security in your code
Protect your AI agent workflows with Arcjet
See how each coding agent handles a hook failure and install the Arcjet wrapper that fails closed on Codex and Cursor.