n8n-io/n8n · warning · Error

Episodic memory requires a resolved embedding model before…

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.

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.

Appendix: 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)