n8n-io/n8n · error · StaleResumeError

Run is not suspended. Cannot resume.

Error message

Run ${this.runId} is not suspended. Cannot resume.

What it means

Thrown by the memory-session-key validator when an AI memory subnode's session key expression uses $json. $json resolves to the current node's input, which is fragile and ambiguous for session keys — it can produce unstable or colliding sessions. The validator requires an explicit node reference so the key is deterministic.

Solutions

  1. Reference the trigger explicitly, e.g. nodeJson(trigger, 'message.chat.id').
  2. Or use the full node reference form $('Trigger').item.json.message.chat.id.
  3. Pin the session key to a stable upstream field that uniquely identifies the conversation/user.

Example fix

// before
memory: windowBufferMemory({
  sessionId: '={{ $json.message.chat.id }}',
}),

// after
memory: windowBufferMemory({
  sessionId: expr("={{ $('Trigger').item.json.message.chat.id }}"),
});
Defensive patterns

Strategy: validation

Validate before calling

function isUnsafeSessionExpression(value: unknown): value is string {
  return typeof value === 'string' && value.includes('$json') && (value.startsWith('=') || value.includes('{{'));
}

// before assigning a session key:
if (isUnsafeSessionExpression(sessionId)) {
  throw new Error('Session key uses $json; use an explicit node reference like $(\'Trigger\').item.json.message.chat.id');
}

Type guard

function isUnsafeSessionExpression(value: unknown): value is string {
  return typeof value === 'string' && value.includes('$json') && (value.startsWith('=') || value.includes('{{'));
}

Prevention

When it happens

Trigger: A session-key parameter value is a string that includes '$json' AND either starts with '=' or contains '{{'. The helper isUnsafeSessionExpression matches that shape, and createIssue is emitted for that parameter path.

Common situations: Wiring a memory subnode (Buffer/Window memory) and pointing the sessionId at {{$json.message.chat.id}}; an AI builder defaults to $json because it is the shortest reference; copying a Telegram-bot tutorial that used $json before this guard existed.

Related errors


AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12). Data as JSON: /api/errors/6d154edf140e44fa. Report an issue: GitHub.

Appendix: source

Thrown at packages/@n8n/agents/src/runtime/loop/agent-runtime.ts:426

					`Cannot decrease maxIterations when resuming a run. Expected >= ${persistedMaxIterations}, received ${callerMaxIterations}.`,
				);
			}

			const mergedMaxIterations = callerMaxIterations ?? persistedMaxIterations;
			const mergedExecOptions: ExecutionOptions & { iterationCount?: number } = {
				...callerExecOptions,
				...(mergedMaxIterations !== undefined ? { maxIterations: mergedMaxIterations } : {}),
				...(state.iterationCount !== undefined ? { iterationCount: state.iterationCount } : {}),
			};

			const resumeOptions: RuntimeExecutionOptions = {
				persistence: state.persistence,
				...mergedExecOptions,
			};

			const claimed = await this.runState.claimResume(this.runId, state);
			if (!claimed) {
				throw new StaleResumeError(`Run ${this.runId} is not suspended. Cannot resume.`);
			}
			await options.onResumeClaimed?.();

			abortScope = this.eventBus.createAbortScope(resumeOptions.abortSignal);
			const activeAbortScope = abortScope;

			const pendingResume: PendingResume = {
				pendingToolCalls: state.pendingToolCalls,
				resumeToolCallId: options.toolCallId,
				resumeData,
			};

			await this.ensureModelCost();

			await this.memory.setListObservationLogMemory(list, state.persistence);

			if (method === 'generate') {
				const sink = new GenerateSink(this.createRunServices());

View on GitHub (pinned to 5ac6606e81)