n8n-io/n8n · error · InvalidRuntimeSkillError

${formatSkillValidationErrors(validation.errors)}

Error message

${formatSkillValidationErrors(validation.errors)}

What it means

Thrown by normalizeRuntimeSkills after parsing succeeds, when the assembled RuntimeSkill fails the deeper validateRuntimeSkill check. That check requires non-empty string values for id, name, description, and instructions. Unlike error 102 (markdown/frontmatter parsing), this guards the in-memory object shape — e.g. an empty instructions body after trim, or a programmatically-constructed skill missing a required field. The raw validation messages are surfaced without a directory prefix.

Source

Thrown at packages/@n8n/agents/src/skills/registry.ts:180

			return `${error.message}${field}${path}.${hint}`;
		})
		.join(' ');
}

function normalizeRuntimeSkills(skills: RuntimeSkill[]): RuntimeSkill[] {
	const sortedSkills = [...skills].sort(compareRuntimeSkills);
	const seenIds = new Set<string>();
	const seenNames = new Set<string>();
	const seenSourceDirectories = new Set<string>();

	return sortedSkills.map((skill) => {
		const normalizedSkill: RuntimeSkill = {
			...skill,
			linkedFiles: normalizeLinkedFiles(skill.linkedFiles),
		};
		const validation = validateRuntimeSkill(normalizedSkill);
		if (!validation.ok) {
			throw new InvalidRuntimeSkillError(formatSkillValidationErrors(validation.errors));
		}

		if (seenIds.has(normalizedSkill.id)) {
			throw new InvalidRuntimeSkillError(`Duplicate skill id "${normalizedSkill.id}"`);
		}
		seenIds.add(normalizedSkill.id);

		const normalizedName = normalizedSkill.name.toLowerCase();
		if (seenNames.has(normalizedName)) {
			throw new InvalidRuntimeSkillError(`Duplicate skill name "${normalizedSkill.name}"`);
		}
		seenNames.add(normalizedName);

		if (normalizedSkill.sourceDirectory) {
			if (seenSourceDirectories.has(normalizedSkill.sourceDirectory)) {
				throw new InvalidRuntimeSkillError(
					`Duplicate skill source directory "${normalizedSkill.sourceDirectory}"`,
				);

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Read the surfaced validation messages to see which of id/name/description/instructions is empty or missing.
  2. If the skill came from a SKILL.md, add non-empty content below the closing frontmatter delimiter (this becomes `instructions`).
  3. If constructed in code, populate all four required string fields with non-empty values before calling createRuntimeSkillSource/createRuntimeSkillRegistry.

Example fix

// before
createRuntimeSkillSource([{
  id: 'billing', name: 'billing', description: 'Billing help', instructions: '',
}]);

// after
createRuntimeSkillSource([{
  id: 'billing', name: 'billing', description: 'Billing help',
  instructions: 'Step-by-step billing guidance...',
}]);
Defensive patterns

Strategy: validation

Validate before calling

import { validateRuntimeSkill, type RuntimeSkill } from '@n8n/agents/skills';

function assertSkillObject(skill: RuntimeSkill): void {
  const result = validateRuntimeSkill(skill);
  if (!result.ok) {
    throw new Error(`Invalid skill object: ${JSON.stringify(result.errors)}`);
  }
}

// Call before createRuntimeSkillSource:
skills.forEach(assertSkillObject);

Type guard

function isCompleteSkill(s: Partial<RuntimeSkill>): s is RuntimeSkill {
  return ['id', 'name', 'description', 'instructions'].every(
    (f) => typeof s[f] === 'string' && (s[f] as string).trim().length > 0,
  );
}

if (!isCompleteSkill(maybeSkill)) throw new Error('Skill missing required non-empty string fields');

Try / catch

try {
  createRuntimeSkillSource(skills);
} catch (err) {
  if (err instanceof InvalidRuntimeSkillError) {
    // normalization-time validation error — fix the skill object, do not retry
    throw new Error(`Skill normalization failed: ${err.message}`);
  }
  throw err;
}

Prevention

When it happens

Trigger: Loading skills programmatically via createRuntimeSkillSource(skills) with a hand-built RuntimeSkill whose `instructions` is an empty string; a SKILL.md whose frontmatter is valid but body is blank (so instructions trims to ''); passing a skill object missing the `id` field.

Common situations: Building a RuntimeSkill in code instead of from markdown and forgetting instructions; a skill file that is all frontmatter and no body; a test fixture with a placeholder empty body; an upstream transform that stripped the body.

Related errors


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