{"record":{"id":"d5be744da277e8f4","repo":"n8n-io/n8n","slug":"gateway-confirmation-required-json-stringify","errorCode":null,"errorMessage":"GATEWAY_CONFIRMATION_REQUIRED::${JSON.stringify({ toolGroup: resource.toolGroup, resource: resource.resource, description: resource.description, options: INSTANCE_RESOURCE_DECISION_KEYS })}","messagePattern":"GATEWAY_CONFIRMATION_REQUIRED::(.+?)\\)\\}","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"info","filePath":"packages/@n8n/computer-use/src/gateway-client.ts","lineNumber":513,"sourceCode":"\t\tconst { session, confirmResourceAccess, config } = this.options;\n\n\t\tfor (const resource of resources) {\n\t\t\tconst rule = session.check(resource.toolGroup, resource.resource);\n\n\t\t\tif (rule === 'deny') {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`User permanently denied access to ${resource.toolGroup}: ${resource.resource}`,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tif (rule === 'allow') continue;\n\n\t\t\tlet resolvedDecision: ResourceDecision;\n\n\t\t\tif (decision && config.permissionConfirmation === 'instance') {\n\t\t\t\tresolvedDecision = decision;\n\t\t\t} else if (config.permissionConfirmation === 'instance') {\n\t\t\t\tthrow new Error(\n\t\t\t\t\t`${GATEWAY_CONFIRMATION_REQUIRED_PREFIX}${JSON.stringify({\n\t\t\t\t\t\ttoolGroup: resource.toolGroup,\n\t\t\t\t\t\tresource: resource.resource,\n\t\t\t\t\t\tdescription: resource.description,\n\t\t\t\t\t\toptions: INSTANCE_RESOURCE_DECISION_KEYS,\n\t\t\t\t\t})}`,\n\t\t\t\t);\n\t\t\t} else {\n\t\t\t\tresolvedDecision = await confirmResourceAccess(resource);\n\t\t\t}\n\n\t\t\tswitch (resolvedDecision) {\n\t\t\t\tcase 'allowOnce':\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'allowForSession':\n\t\t\t\t\tsession.allowForSession(resource.toolGroup, resource.resource);\n\t\t\t\t\tbreak;\n\t\t\t\tcase 'alwaysAllow':","sourceCodeStart":495,"sourceCodeEnd":531,"githubUrl":"https://github.com/n8n-io/n8n/blob/5ac6606e81f67bb9534255570cd4e86fd8101eee/packages/@n8n/computer-use/src/gateway-client.ts#L495-L531","documentation":"This is a structured control-flow signal, not a real error. Thrown by checkPermissions() when config.permissionConfirmation === 'instance' and no pre-supplied decision was passed. The message starts with 'GATEWAY_CONFIRMATION_REQUIRED::' followed by a JSON payload describing the resource, its description, and the available decision options. It is designed to be caught by the gateway message handler and forwarded to the n8n UI for interactive user confirmation.","triggerScenarios":"A tool is invoked in instance-confirmation mode, the resource is neither pre-approved nor pre-denied in the session, and no decision was included in the gateway tool-call message. This is the normal first-access flow for any new resource.","commonSituations":"First access to a new file path, browser domain, or shell command in instance-confirmation mode — the user has not yet seen a confirmation prompt for this resource.","solutions":["Catch errors whose message starts with GATEWAY_CONFIRMATION_REQUIRED_PREFIX","Parse the JSON payload after the prefix to get toolGroup, resource, description, and options","Forward the confirmation request to the n8n instance UI via the gateway response channel","Resume the tool call with the user's ResourceDecision once they respond"],"exampleFix":"// before (unhandled):\ntry { await gateway.callTool(name, args); }\ncatch (e) { throw e; // loses the confirmation signal }\n\n// after:\ntry {\n  await gateway.callTool(name, args);\n} catch (e) {\n  if (e.message?.startsWith('GATEWAY_CONFIRMATION_REQUIRED::')) {\n    const payload = JSON.parse(e.message.slice('GATEWAY_CONFIRMATION_REQUIRED::'.length));\n    await sendConfirmationToUI(payload);\n    // resume with the user's decision\n  } else throw e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"const GATEWAY_CONFIRMATION_REQUIRED_PREFIX = 'GATEWAY_CONFIRMATION_REQUIRED::';\n\nfunction isConfirmationRequired(e: unknown): e is Error & { payload: object } {\n  return e instanceof Error && e.message.startsWith(GATEWAY_CONFIRMATION_REQUIRED_PREFIX);\n}\n\nfunction parseConfirmationPayload(e: Error): {\n  toolGroup: string; resource: string; description: string; options: string[];\n} {\n  return JSON.parse(e.message.slice(GATEWAY_CONFIRMATION_REQUIRED_PREFIX.length));\n}","tryCatchPattern":"try {\n  return await gatewayClient.callTool(name, args);\n} catch (e) {\n  if (e instanceof Error && e.message.startsWith('GATEWAY_CONFIRMATION_REQUIRED::')) {\n    const payload = JSON.parse(e.message.slice('GATEWAY_CONFIRMATION_REQUIRED::'.length));\n    // Forward to the n8n instance UI for user confirmation\n    const decision = await forwardToInstanceUI(payload);\n    // Retry with the user's decision\n    return await gatewayClient.callTool(name, args, decision);\n  }\n  throw e;\n}","preventionTips":["Always handle GATEWAY_CONFIRMATION_REQUIRED as a control-flow signal, not an error","When calling from instance mode, supply the decision parameter if available to avoid the round-trip","Parse the JSON payload to extract toolGroup, resource, and available decision options for the UI"],"tags":["permissions","control-flow","computer-use"],"backgroundTag":null,"analyzedSha":"5ac6606e81f67bb9534255570cd4e86fd8101eee","analyzedAt":"2026-08-12T05:26:35.080Z","schemaVersion":2},"datasetVersion":"2026-08-12T13:17:24.610Z"}