vercel/ai · warning · CodeModeToolApprovalDeniedError
CODE_MODE_TOOL_APPROVAL_DENIED
CODE_MODE_TOOL_APPROVAL_DENIED
Error message
Tool "${toolName}" approval was denied${reason ? `: ${reason}` : '.'} What it means
When a code-mode run resumes from an approval interrupt, invokeCodeModeTool normalizes the stored resolution and, if it records approval as denied, throws CodeModeToolApprovalDeniedError carrying the tool name, input, tool call id, and the denial reason. This surfaces the human's 'no' decision back into the sandboxed program so it can handle the rejection instead of silently executing the tool.
Source
Thrown at packages/code-mode/src/run-code-mode.ts:291
input.toolExecutionOptions?.context;
const baseExecutionOptions: CodeModeToolExecutionOptions = {
toolCallId: outerToolCallId,
messages: input.toolExecutionOptions?.messages ?? [],
abortSignal: context.abortSignal,
...(forwardedContext === undefined ? {} : { context: forwardedContext }),
...(forwardedExperimentalContext === undefined
? {}
: { experimental_context: forwardedExperimentalContext }),
};
let codeModeInterrupt: CodeModeInterruptExecutionContext | undefined;
let skipApproval = false;
if (context.resume !== undefined) {
const payload = assertInterruptPayload(context.resume.payload);
if (isCodeModeApprovalInterruptPayload(payload)) {
const decision = normalizeApprovalResolution(context.resume.resolution);
if (!decision.approved) {
throw new CodeModeToolApprovalDeniedError(
toolName,
toolInput,
toolCallId,
decision.reason,
);
}
skipApproval = true;
} else {
codeModeInterrupt = {
interruptId: `${toolCallId}:interrupt`,
payload,
resolution: context.resume.resolution,
};
}
}
try {
const inputJson = toJsonPayload(View on GitHub (pinned to 69428b1f8b)
Solutions
- This is an expected, intended outcome — catch CodeModeToolApprovalDeniedError (isInstance) in the host and in sandbox code handle the denial branch.
- If the denial was accidental, re-issue a fresh run/approval request and resume with { approved: true }.
- Include a clear reason in the resolution so sandbox code and logs can distinguish denial causes.
Example fix
// before
await continueCodeModeInterrupt({ interrupt, resolution: decision, tools }); // crashes on deny
// after
try {
await continueCodeModeInterrupt({ interrupt, resolution: decision, tools });
} catch (error) {
if (CodeModeToolApprovalDeniedError.isInstance(error)) {
return { status: 'denied', reason: error.reason };
}
throw error;
} Defensive patterns
Strategy: try-catch
Validate before calling
// Before resuming, inspect the resolution you are about to submit:
if (resolution && resolution.approved === false) {
// expect a CodeModeToolApprovalDeniedError; branch your UI accordingly
} Type guard
function isDenied(resolution: unknown): boolean {
return typeof resolution === 'object' && resolution !== null && (resolution as { approved?: unknown }).approved === false;
} Try / catch
try {
return await continueCodeModeInterrupt({ interrupt, resolution, tools });
} catch (error) {
if (CodeModeToolApprovalDeniedError.isInstance(error)) {
return { status: 'denied', toolName: error.toolName, reason: error.reason };
}
throw error;
} Prevention
- Treat denial as a first-class outcome in human-in-the-loop flows, not an exception path.
- Record the denial reason in the resolution for audit logs.
- In sandbox code, wrap tool calls in try/catch to handle denied approvals gracefully.
When it happens
Trigger: Resuming a code-mode run via continueCodeModeInterrupt (or interruptResolution) where the resolution for an approval-kind interrupt is { approved: false, reason } — e.g. a user clicked 'Deny' in an approval UI.
Common situations: Human-in-the-loop approval flows where reviewers reject sensitive tool calls (deleting data, spending money); automated policy gates that deny approvals outside business hours; applications that resume with a default 'deny' resolution after a timeout.
Related errors
- Tool call "${toolCallId}" not found for approval request "${
- No tool invocation found for approval ID "${approvalId}".
- CODE_MODE_PROTOCOL_ERROR
- CODE_MODE_PROTOCOL_ERROR
- Continuation contains a non-JSON-serializable value.
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/67fc4524594b2c4e.
Report an issue: GitHub.