needsApproval and LangGraph interrupt() aren't a security policy. A click is a hold, not a remote allow or deny. Hosted requireApproval is the same class of control. The live human-approval page covers Eve, Mastra, and Claude; this page extends it to OpenAI Agents and LangGraph.Why aren't needsApproval and interrupt() a security policy?
needsApproval and LangGraph interrupt() park a call for a person. A policy is a repeatable allow or deny on this tool, these arguments, and this identity. A click is a person, this once.
Fatigue turns the hold into an allowlist. The reviewer sees twenty lookup_order calls and one refund. They click through. The refund already has model context they never screened.
The same trap is already documented for Eve user-approval, Mastra requireApproval, and Claude canUseTool. For more information about that page, see Human approval is not a security policy. This page extends it to OpenAI Agents and LangGraph. It doesn't replace it.
protect() is the HTTP check on a route. guard() is the check on a tool or a call that has no request. Neither is a Slack DM to the on-call.
What does OpenAI Agents needsApproval do?
OpenAI Agents needsApproval pauses the run and returns interruptions. A person calls state.approve or state.reject. That click is human-in-the-loop. It isn't guardTool.
Hosted MCP requireApproval is the same class of control. It parks a hosted tool. It doesn't evaluate an Arcjet rule. There is no guardApproval on this adapter. Don't wrap needsApproval as Guard.
A pasted ticket can still start run() if you never screened the string. needsApproval doesn't read that prompt. For more information about the inbound check, see How do I screen inbound prompts in OpenAI Agents SDK?. For more information about the authored-tool deny, see How do I secure an OpenAI Agents SDK agent?.
What does hosted requireApproval do?
Hosted requireApproval pauses a hosted MCP tool until a person answers. The hosted tool still has no local invoke for guardTool to wrap. Web search, file search, code interpreter, handoffs, agent.asTool(), and computer/shell are the same gap: they aren't deny points on the OpenAI Agents adapter.
A person who approves webSearchTool has approved a hosted call. They haven't applied a labeled policy with application context. Runner agent_tool_start is observe-only. It can't refuse the host.
If the only control is the hosted click, the reviewer approved a search on a prompt they never saw.
What does LangGraph interrupt() do?
LangGraph interrupt() and interrupt_before=["tools"] pause the graph. A person resumes it. That is a hold, not a remote policy. There is no guardInterrupt and no guardApproval.
Graph hooks and HITL pauses can't stop tool.invoke inside ToolNode. MCP and unwrapped tools run there. The deny is guardToolNode, wrapped in place. For more information about that recipe, see How do I secure MCP tools in LangGraph?.
A Linear comment that posts while the graph is "waiting for approval" on a different node is the usual miss. The pause wasn't on that invoke.
What has to sit in front of the click?
A remote allow or deny has to sit in front of the click. Screen inbound text before run() or graph.invoke. Deny the authored tool or the ToolNode call before the host is contacted. Then, for the few irreversible allows, wait for a person.
Runtime security for LLM applications already splits prompt injection, exfiltration, and unsafe actions. The click is a hold after allow.
Direct guard() fails open. Wrappers (guardTool, guardToolNode) default to onGuardError: "deny". Gate hasFailedOpen() on a bare inbound guard() when that site must fail closed. A DENY conclusion always blocks on those wrappers whatever onGuardError says, so the setting only decides what happens when the policy couldn't be evaluated at all.
Park the small set that can't be undone: a charge, a public pull request, a mail to a customer. Don't park every lookup_order. Human approval is not a security policy stays the Eve, Mastra, and Claude write-up. For more information about how to build that hold, see Human approval gates. If the only control is the click, the reviewer approved send() on a prompt they never saw.
Frequently asked questions
needsApproval and LangGraph interrupt() are not a security policy
needsApproval and LangGraph interrupt() park a call for a person. A policy is a repeatable allow or deny on this tool, these arguments, and this identity. A click is a person, this once. Hosted requireApproval is the same hold.
Why aren't needsApproval and interrupt() a security policy?
A policy is a repeatable decision. A click is a person, this once. Fatigue turns the hold into an allowlist, and a person can't fire on the calls that never reach them.
Is hosted requireApproval a policy gate?
No. Hosted requireApproval pauses a hosted MCP tool until a person answers. Hosted tools have no local invoke for guardTool. Runner agent_tool_start is observe-only.
Can LangGraph interrupt() stop tool.invoke inside ToolNode?
No. Graph hooks and HITL pauses can't stop tool.invoke. MCP and unwrapped tools run inside ToolNode. The deny is guardToolNode.
Is this the same as the live human-approval page?
Same trap, new stacks. The live page covers Eve user-approval, Mastra requireApproval, and Claude canUseTool. This page extends that to OpenAI Agents and LangGraph. Read both.
AI runtime security in your code
Protect your AI agent workflows with Arcjet
Get allow, deny, and redact on agent actions before the side effect.