langchain-ai/deepagents · error · ClientHookStopError
Permission interrupted by hook
Error message
Permission interrupted by hook
What it means
During HITL interrupt processing, if a hook's decision interrupts a permission request, `_process_hitl_interrupts` raises `ClientHookStopError` with the hook-supplied message, defaulting to "Permission interrupted by hook". It signals that a client lifecycle hook, not the user, denied/interrupted the pending permission prompt, ending the headless run.
Source
Thrown at libs/code/deepagents_code/client/non_interactive.py:1860
[
ToolCallData(
id=f"{interrupt_id}:{index}",
name=action_request.get("name", ""),
args=action_request.get("args", {}),
)
for index, action_request in enumerate(action_requests)
]
)
if plan.interrupted:
interrupting = next(
outcome for outcome in plan.outcomes if outcome.interrupt
)
reason = (
interrupting.decision.get("message")
if interrupting.decision is not None
else None
)
raise ClientHookStopError(reason or "Permission interrupted by hook")
if not plan.fully_resolved:
await state.hooks.notify(
DcodeNotificationKind.PERMISSION_REQUIRED,
"Permission required",
)
resolved: list[dict[str, str]] = []
for outcome, action_request in zip(
plan.outcomes,
action_requests,
strict=True,
):
decision = outcome.decision
resolved.append(
cast("dict[str, str]", dict(decision))
if decision is not None
else _make_hitl_decision(action_request, console)
)View on GitHub (pinned to a1af029e6e)
Solutions
- Read the hook's `message` if present; if you see the default text, find which registered permission hook interrupted and inspect its logs.
- Update the hook to allow the operation or to emit a descriptive `message` in its decision.
- Allow-list the needed command/paths via `shell.allow_list` so the permission never reaches the hook.
- Catch `ClientHookStopError` in the harness and record it as a policy denial rather than a crash.
Example fix
// before: interrupt with no message
return {"decision": "interrupt"}
// after: explain the denial
return {"decision": "interrupt", "message": "command not on approved list: " + cmd} Defensive patterns
Strategy: try-catch
Validate before calling
# ensure hooks won't interrupt needed commands before the run
for hook in active_hooks:
if not hook.allows(command): disable(hook) Try / catch
from deepagents_code.hooks.client_lifecycle import ClientHookStopError
try:
await run_non_interactive(task)
except ClientHookStopError as exc:
record_policy_denial(exc) # treat as deliberate denial, default message = hook sent none Prevention
- Require hooks to include a `message` in interrupt decisions
- Maintain `shell.allow_list` so routine commands skip the permission/hook path
- Log hook decisions in headless runs for auditability
When it happens
Trigger: A permission request is intercepted by a registered hook whose decision interrupts the tool call while running `_run_agent_loop` headless; the interrupting hook's decision contains no `message`, so the default text is used.
Common situations: Policy hooks blocking shell commands not on the allow list; a hook returning an interrupt decision without a custom message; security tooling (e.g. secret-scanning hooks) vetoing file edits in headless CI runs.
Related errors
- {message}
- Expected PermissionRequestDecision, got {type(decision).__na
- Hook resume invocation_id mismatch: expected {invocation_id}
- Hook resume snapshot_id mismatch: expected {snapshot_id}, go
- Received hook invocation interrupt without a HooksRuntime
AI-assisted analysis of langchain-ai/deepagents@a1af029e6e (2026-08-29).
Data as JSON: /api/errors/e833ef5a9e1100a9.
Report an issue: GitHub.