n8n-io/n8n · error · Error

Could not read node catalogue at ${jsonPath}: ${message} Run

Error message

Could not read node catalogue at ${jsonPath}: ${message}
Run `pnpm export:nodes` in packages/@n8n/ai-workflow-builder.ee to generate it, or pass --nodes-json <path> to point at an existing file.

What it means

Thrown by loadNodeCatalogue when readFile fails on the catalogue JSON path. The error wraps the underlying filesystem message and tells the operator exactly how to regenerate the file (pnpm export:nodes) or point at an existing one via --nodes-json. It does not fire for malformed-but-readable JSON — that is a separate error.

Source

Thrown at packages/@n8n/instance-ai/evaluations/harness/stub-services.ts:341

}

// ---------------------------------------------------------------------------
// Helpers
// ---------------------------------------------------------------------------

interface NodeCatalogue {
	searchableNodes: SearchableNodeDescription[];
	/** Indexed by node `name` for O(1) `getDescription` lookups. */
	descriptionsByName: Map<string, NodeDescription>;
}

async function loadNodeCatalogue(jsonPath: string): Promise<NodeCatalogue> {
	let content: string;
	try {
		content = await fs.readFile(jsonPath, 'utf8');
	} catch (error) {
		const message = error instanceof Error ? error.message : String(error);
		throw new Error(
			`Could not read node catalogue at ${jsonPath}: ${message}\n` +
				'Run `pnpm export:nodes` in packages/@n8n/ai-workflow-builder.ee to generate it, ' +
				'or pass --nodes-json <path> to point at an existing file.',
		);
	}

	const parsed = jsonParse<unknown>(content, {
		errorMessage: `Could not parse node catalogue at ${jsonPath} as JSON`,
	});
	if (!Array.isArray(parsed)) {
		throw new Error(`Expected ${jsonPath} to contain a JSON array of node descriptions.`);
	}

	const searchableNodes: SearchableNodeDescription[] = [];
	const descriptionsByName = new Map<string, NodeDescription>();
	for (const entry of parsed) {
		const searchable = coerceSearchableNode(entry);
		searchableNodes.push(searchable);

View on GitHub (pinned to 5ac6606e81)

Solutions

  1. Run `pnpm export:nodes` in packages/@n8n/ai-workflow-builder.ee to generate the catalogue at the expected location.
  2. If a catalogue exists elsewhere, pass `--nodes-json <path>` to the harness to point at it.
  3. Read the embedded underlying message to distinguish ENOENT (missing) from EACCES (permissions).

Example fix

# before — run evals without generating the catalogue
pnpm eval

# after — generate first, then run
cd packages/@n8n/ai-workflow-builder.ee && pnpm export:nodes && cd -
pnpm eval
# or: pnpm eval --nodes-json /path/to/existing/nodes.json
Defensive patterns

Strategy: validation

Validate before calling

import { existsSync, statSync } from 'node:fs';

function catalogueReadable(path: string): boolean {
  try {
    return existsSync(path) && statSync(path).isFile();
  } catch {
    return false;
  }
}

if (!catalogueReadable(path)) {
  throw new Error(`node catalogue missing at ${path}; run pnpm export:nodes or pass --nodes-json`);
}

Type guard

function isNodeCatalogueReadError(e: unknown): boolean {
  return e instanceof Error && e.message.startsWith('Could not read node catalogue at ');
}

Try / catch

try {
  await loadNodeCatalogue(path);
} catch (e) {
  if (e instanceof Error && e.message.startsWith('Could not read node catalogue')) {
    // run `pnpm export:nodes` or pass --nodes-json, then retry
  }
  throw e;
}

Prevention

When it happens

Trigger: The catalogue path does not exist (most common — first run, or after a clean checkout); the path is a directory or lacks read permissions; the --nodes-json flag was not passed and the default location is empty.

Common situations: First eval run on a fresh clone without running export:nodes; CI job that skips the catalogue-generation step; relative path resolved against the wrong cwd.

Related errors


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