n8n-io/n8n · error · Error
Tool "${this.name}" has .resume() but missing .suspend()
Error message
Tool "${this.name}" has .resume() but missing .suspend() What it means
Tool.build() enforces that resume and suspend schemas are declared together. .resume(schema) defines the payload accepted when resuming a suspended tool, but a resume schema without a corresponding .suspend(schema) means there is no defined payload the tool yields to pause — the resume path could never be reached. The build rejects the orphan resume schema early.
Source
Thrown at packages/@n8n/agents/src/sdk/tool.ts:377
}
if (!this.desc) {
throw new Error(`Tool "${this.name}" requires a description`);
}
if (!this.inputSchema) {
throw new Error(`Tool "${this.name}" requires an input schema`);
}
if (!this.handlerFn) {
throw new Error(`Tool "${this.name}" requires a handler`);
}
const hasSuspend = this.suspendSchemaValue !== undefined;
const hasResume = this.resumeSchemaValue !== undefined;
if (hasSuspend && !hasResume) {
throw new Error(`Tool "${this.name}" has .suspend() but missing .resume()`);
}
if (hasResume && !hasSuspend) {
throw new Error(`Tool "${this.name}" has .resume() but missing .suspend()`);
}
const hasApproval =
(this.requireApprovalValue ?? false) || this.needsApprovalFnValue !== undefined;
if (hasApproval && (hasSuspend || hasResume)) {
throw new Error(
`Tool "${this.name}" cannot use both approval (.requireApproval/.needsApprovalFn) and suspend/resume (.suspend/.resume)`,
);
}
const built: BuiltTool = {
name: this.name,
description: this.desc,
systemInstruction: this.systemInstructionText,
suspendSchema: this.suspendSchemaValue,
resumeSchema: this.resumeSchemaValue,
handleCancellation: this.handleCancellationValue,
toMessage: this.toMessageFn as (output: unknown) => AgentMessage | undefined,View on GitHub (pinned to 5ac6606e81)
Solutions
- Add the matching .suspend(z.object({...})) defining the payload your handler yields when pausing.
- If resume was added by mistake, remove the .resume() call entirely.
- Confirm the suspend schema your handler yields matches (structurally) the resume schema the resume path will validate against.
Example fix
// before
const t = new Tool('ask_user')
.description('Ask the user')
.input(z.object({ q: z.string() }))
.resume(z.object({ answer: z.string() }))
.handler(...); // missing .suspend()
// after
const t = new Tool('ask_user')
.description('Ask the user')
.input(z.object({ q: z.string() }))
.suspend(z.object({ q: z.string() }))
.resume(z.object({ answer: z.string() }))
.handler(...); Defensive patterns
Strategy: validation
Validate before calling
function interruptibleTool(name: string, suspendSchema: unknown, resumeSchema: unknown) {
if ((suspendSchema && !resumeSchema) || (resumeSchema && !suspendSchema)) {
throw new Error('suspend and resume schemas must be provided together');
}
const t = new Tool(name);
if (suspendSchema) t.suspend(suspendSchema as any);
if (resumeSchema) t.resume(resumeSchema as any);
return t;
} Type guard
function isPairedSuspendResume(suspend?: unknown, resume?: unknown): boolean {
return (suspend !== undefined) === (resume !== undefined);
} Prevention
- Treat suspend and resume as a single interrupt declaration — never edit one without the other.
- Use a code-search check before merging: any .suspend( must have a matching .resume( in the same builder chain.
- Document the resume payload contract next to the suspend schema so the pairing isn't accidentally split.
When it happens
Trigger: Calling .resume(z.object({...})) without a matching .suspend(...), then building. Fires after the suspend-without-resume check, so the message specifically names .resume() as the orphan.
Common situations: Copy-pasting a resume schema from another tool without its suspend counterpart; refactoring that removes .suspend() but leaves .resume(); misunderstanding the pairing requirement.
Related errors
- Tool "${this.name}" has .suspend() but missing .resume()
- Tool "${this.name}" cannot use both approval (.requireApprov
- Tool name is required
- Tool "${this.name}" requires a description
- Tool "${this.name}" requires an input schema
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/580bbd130b8707b8.
Report an issue: GitHub.