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
- Move the secret into an n8n credential and reference it from the consuming node instead of a Set field.
- Rename the field to something non-credential-like if it is not actually a secret (e.g. 'tokenCount').
- 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
- Never store real secrets in Set fields; use the credential system.
- Avoid credential-like field names (api_key, token, secret) for non-secrets.
- Scan generated assignments for credential-like names before persisting.
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
- LangSmithTelemetry creates its own tracer — do not use .otlp
- Invalid resume payload: ${parseResult.error}
- Agent run was aborted
- ${turn.errorReason.message}
- Tool name "${RECALL_MEMORY_TOOL_NAME}" is reserved while epi
AI-assisted analysis of n8n-io/n8n@5ac6606e81 (2026-08-12).
Data as JSON: /api/errors/d6e8373cc7456bb1.
Report an issue: GitHub.