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

  1. This is an expected, intended outcome — catch CodeModeToolApprovalDeniedError (isInstance) in the host and in sandbox code handle the denial branch.
  2. If the denial was accidental, re-issue a fresh run/approval request and resume with { approved: true }.
  3. 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

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


AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30). Data as JSON: /api/errors/67fc4524594b2c4e. Report an issue: GitHub.