n8n-io/n8n · warning · Error

Episodic memory requires a resolved embedding model before r

Error message

Episodic memory requires a resolved embedding model before runtime use.

What it means

Warning thrown by the Set node validator when an assignment's name matches a credential-like pattern (api_key, token, secret, password, credentials, auth, etc.). Storing secrets as plain Set fields leaks them into the execution data and workflow JSON instead of the encrypted credential store.

Source

Thrown at packages/@n8n/agents/src/runtime/memory/episodic-memory.ts:108

	memory: BuiltMemory,
): memory is BuiltMemory & BuiltEpisodicMemoryStore {
	const episodic = memory.episodic;
	return (
		episodic !== undefined &&
		typeof episodic.saveEntryWithSources === 'function' &&
		typeof episodic.searchEntries === 'function' &&
		typeof episodic.getEntrySources === 'function' &&
		typeof episodic.applyReflection === 'function' &&
		typeof episodic.getCursor === 'function' &&
		typeof episodic.setCursor === 'function'
	);
}

export function withEpisodicMemoryDefaults(
	config: EpisodicMemoryConfig,
): NormalizedEpisodicMemoryConfig {
	if (!config.embedder) {
		throw new Error('Episodic memory requires a resolved embedding model before runtime use.');
	}

	return {
		topK: config.topK ?? DEFAULT_EPISODIC_MEMORY_TOP_K,
		maxEntriesPerRun: config.maxEntriesPerRun ?? DEFAULT_EPISODIC_MEMORY_MAX_ENTRIES_PER_RUN,
		embedder: config.embedder,
		embeddingModel: config.embeddingModel ?? 'custom',
		extract: config.extract,
		reflect: config.reflect,
		recallToolInstruction:
			config.prompts?.recallToolInstruction ?? DEFAULT_EPISODIC_MEMORY_RECALL_TOOL_INSTRUCTION,
	};
}

export async function runEpisodicMemoryIndexer(
	opts: RunEpisodicMemoryIndexerOpts,
): Promise<RunEpisodicMemoryIndexerResult> {
	if (!isEpisodicMemoryEnabled(opts.config)) return { status: 'skipped', reason: 'disabled' };

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Move the secret into an n8n credential and reference it from the consuming node instead of a Set field.
  2. Rename the field to something non-credential-like if it is not actually a secret (e.g. 'tokenCount').
  3. If it genuinely must be a field, confirm it holds no real secret before suppressing.

Example fix

// before
set({
  name: 'Config',
  assignments: {
    assignments: [{ id: '1', name: 'api_key', value: 'live_key', type: 'string' }],
  },
});

// after — store the secret in a credential, reference a non-secret field name
set({
  name: 'Config',
  assignments: {
    assignments: [{ id: '1', name: 'region', value: 'us', type: 'string' }],
  },
});
Defensive patterns

Strategy: validation

Validate before calling

import { isCredentialFieldName } from './validation-helpers';

function findCredentialFieldAssignments(items: Array<{ name?: string }>): string[] {
  return items.filter((i) => typeof i.name === 'string' && isCredentialFieldName(i.name)).map((i) => i.name!);
}

const risky = findCredentialFieldAssignments(assignmentItems);
if (risky.length) throw new Error(`Rename or move to credentials: ${risky.join(', ')}`);

Prevention

When it happens

Trigger: For a record assignment, isNonEmptyString(assignment.name) && isCredentialFieldName(assignment.name) is true. The regex set matches api_key/access_token/auth_token/bearer_token/secret_key/private_key/client_secret/password/credentials, or exactly token/secret/auth.

Common situations: An AI builder sets a field named 'api_key' or 'token' to pass a secret downstream; a workaround that avoided creating a credential; copying field names from a vendor payload.

Related errors


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