vercel/ai · error · InvalidToolApprovalSignatureError
Tool approval signature verification failed for approval "${
Error message
Tool approval signature verification failed for approval "${approvalId}" (tool call "${toolCallId}"): ${reason} What it means
When a toolApprovalSecret is configured, every approved tool approval must carry a valid cryptographic signature. validateApprovedToolApprovals throws InvalidToolApprovalSignatureError when the approval request has no signature at all (reason 'missing signature', and other reasons for malformed payloads). This protects against tampered approvals being replayed into the tool loop.
Source
Thrown at packages/ai/src/generate-text/validate-tool-approvals.ts:65
>;
}> {
const approved: Array<CollectedToolApprovals<TOOLS>> = [];
const denied: Array<CollectedToolApprovals<TOOLS>> = [];
const invalid: Array<
CollectedToolApprovals<TOOLS> & { error: InvalidToolInputError }
> = [];
for (const approval of approvedToolApprovals) {
const { toolCall, approvalRequest } = approval;
// Look up the tool by own property only: `toolName` comes from
// client-supplied history, so a name matching an inherited object property
// (e.g. `constructor`, `toString`) must resolve to "no such tool" rather
// than a prototype value that would silently skip input validation below.
const tool = getOwn(tools, toolCall.toolName);
if (toolApprovalSecret != null) {
if (approvalRequest.signature == null) {
throw new InvalidToolApprovalSignatureError({
approvalId: approvalRequest.approvalId,
toolCallId: toolCall.toolCallId,
reason: 'missing signature',
});
}
const valid = await verifyToolApprovalSignature({
secret: toolApprovalSecret,
signature: approvalRequest.signature,
approvalId: approvalRequest.approvalId,
toolCallId: toolCall.toolCallId,
toolName: toolCall.toolName,
input: toolCall.input,
});
if (!valid) {
throw new InvalidToolApprovalSignatureError({
approvalId: approvalRequest.approvalId,View on GitHub (pinned to 69428b1f8b)
Solutions
- Only send approval objects that came from the SDK's signed approval request (same run with toolApprovalSecret enabled).
- Enable toolApprovalSecret consistently across all runs that share the conversation, including the run that produced the approval requests.
- Re-issue the approval by resuming from the original provider response rather than reconstructing approvals manually.
- Catch AI_InvalidToolApprovalSignatureError and surface a security warning; do not retry with the same payload.
Example fix
// before
await result.sendToolApprovals([{ approvalId, approved: true }]); // hand-built, unsigned
// after
const approvals = toolApprovalsFromResponse(originalResult, { approved: true }); // signed by SDK
await result.sendToolApprovals(approvals); Defensive patterns
Strategy: try-catch
Validate before calling
if (toolApprovalSecret != null && approvals.some(a => a.signature == null)) {
throw new Error('Refusing to send unsigned approvals while toolApprovalSecret is enabled');
} Type guard
function isSignedApproval(a: unknown): a is { approvalId: string; signature: string } {
return (
typeof a === 'object' && a !== null &&
typeof (a as any).approvalId === 'string' &&
typeof (a as any).signature === 'string'
);
} Try / catch
try {
await result.sendToolApprovals(approvals);
} catch (error) {
if (InvalidToolApprovalSignatureError.isInstance(error)) {
// treat as security event: do not retry, re-issue approvals from the signed source
logger.security('unsigned-or-tampered tool approval', error);
} else throw error;
} Prevention
- Enable toolApprovalSecret consistently across every service/run handling the conversation.
- Never hand-construct approval objects; always resume from the SDK's signed response.
- Version your approvals when changing the signing scheme and migrate old conversations.
When it happens
Trigger: Passing tool approval responses (from `sendToolApprovals`/multi-step continue) whose approval request lacks a signature while `toolApprovalSecret` is set — e.g. approvals produced by a previous run without the secret, hand-constructed approvals, or approvals serialized before signing existed.
Common situations: Mixing conversations generated before enabling toolApprovalSecret with a run that enables it; manually crafting approval objects in tests/scripts; a proxy/client stripping the signature field; different secrets/versions across services sharing conversation state.
Related errors
- Tool approval response references unknown approvalId: "${app
- ACP runtime environment key ${JSON.stringify(key)} cannot be
- Invalid Cline history file name: ${historyFileName}
- Invalid Cline history file name: ${input.historyFileName}
- Invalid Pi ${label} name: ${name}
AI-assisted analysis of vercel/ai@69428b1f8b (2026-08-30).
Data as JSON: /api/errors/212035be3359dd89.
Report an issue: GitHub.