n8n-io/n8n · error · NodeOperationError
Invalid hitl input for tool ${toolkitTool.name}
Error message
Invalid hitl input for tool ${toolkitTool.name} What it means
Thrown by the ToolExecutor node during gated-tool (human-in-the-loop) handling when the resolved hitl input for the given node is a string rather than an object. Gated tools expect structured approval/parameter data; a string input is not addressable by field and is rejected before being passed to the tool.
Source
Thrown at packages/@n8n/nodes-langchain/nodes/ToolExecutor/ToolExecutor.node.ts:131
try {
for (const tool of toolInputs) {
// Handle toolkits
if (tool && typeof (tool as Toolkit).getTools === 'function') {
const toolsInToolkit = (tool as Toolkit).getTools();
for (const toolkitTool of toolsInToolkit) {
if (!(toolkitTool instanceof Tool || toolkitTool instanceof StructuredTool)) {
continue;
}
if (toolName === toolkitTool.name) {
if (hasGatedToolNodeName(toolkitTool.metadata) && node) {
const toolInput: { toolParameters: unknown } = {
toolParameters: getQueryData(toolName) ?? {},
};
const hitlInput = getQueryData(node);
if (typeof hitlInput === 'string') {
throw new NodeOperationError(
this.getNode(),
`Invalid hitl input for tool ${toolkitTool.name}`,
);
}
// handle code tool which uses a string input, but it should be converted to an object
const requiresObjectInput =
toolkitTool.metadata.originalSchema &&
toolkitTool.metadata.originalSchema instanceof ZodObject;
if (typeof toolInput.toolParameters === 'string' && requiresObjectInput) {
toolInput.toolParameters = convertValueBySchema(
toolInput.toolParameters,
toolkitTool.metadata.originalSchema,
);
}
const hitlMetadata = extractHitlMetadata(
toolkitTool.metadata,View on GitHub (pinned to 5ac6606e81)
Solutions
- Ensure the resume/approval payload for the gated node is a JSON object matching the tool's expected input shape.
- If using a Webhook/Form to resume, parse the body into an object before submitting.
- Validate the wait-node output schema and align it with what the gated tool expects.
Example fix
// before: hitl resume value = "approved"
// after: hitl resume value = { "approved": true, "comment": "ok" } Defensive patterns
Strategy: type-guard
Validate before calling
const hitlInput = getQueryData(node);
if (typeof hitlInput === 'string') throw new Error('Provide HITL input as a JSON object'); Type guard
function isHitlObject(v): v is Record<string, unknown> {
return v != null && typeof v === 'object' && !Array.isArray(v);
} Prevention
- Always resume gated tools with a JSON object payload, not a string.
- Parse webhook/form resume bodies into objects before submitting.
- Align the resume payload schema with the gated tool's expected input.
When it happens
Trigger: Inside the toolkit loop, when toolName === toolkitTool.name, hasGatedToolNodeName(metadata) && node is true, getQueryData(node) returns hitlInput. If `typeof hitlInput === 'string'`, it throws NodeOperationError 'Invalid hitl input for tool <name>'. Fires when the HITL approval payload coming back for the gated node is a plain string.
Common situations: The waiting/resume workflow submits a string approval value instead of a JSON object; the front-end or webhook resume payload is malformed; an expression that should yield an object yields its stringified form; mismatch between the gated tool's expected schema and the resume data.
Related errors
- Cannot decrease maxIterations when resuming a run. Expected
- Checkpoint for runId ${this.runId} has pending tool calls —
- Model ID is required
- Invalid model ID "${rawId}": expected "provider/model-name"
- Unsupported provider: "${provider}". Supported providers: ${
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/f0bc08a33fad99ad.
Report an issue: GitHub.