n8n-io/n8n · error · Error

Eval " " requires either .check() or .judge()

Error message

Eval "${this.evalName}" requires either .check() or .judge()

What it means

Validation guard in the Eval builder's build() step, invoked lazily via ensureBuilt. It fires when an Eval was configured with neither a .check() function nor a .judge() function, so there is no way to evaluate results. It is a configuration-completeness sentinel: the eval object is otherwise well formed but unusable.

Solutions

  1. Add .check(fn) for deterministic scoring or .judge(fn) plus .model(id) plus .credential(name) for LLM-as-judge.
  2. Gate eval construction on the condition that selects a scoring mode so the builder always ends with one.
  3. Assert in tests that new Eval(name) chains always terminate with .check or .judge.

Example fix

// before
const e = new Eval('json').description('validates json');
await e.run({ input: '', output: '{}', expected: '' });
// after
const e = new Eval('json').description('validates json').check(({ output }) => {
  try { JSON.parse(output); return { score: 1, reasoning: 'ok' }; }
  catch { return { score: 0, reasoning: 'bad' }; }
});
Defensive patterns

Strategy: validation

Validate before calling

function assertEvalReady(e: { checkFn?: unknown; judgeFn?: unknown }) {
  if (!e.checkFn && !e.judgeFn) {
    throw new Error('Eval has no scoring function; call .check() or .judge()');
  }
}

Type guard

function evalHasScorer(e: { checkFn?: unknown; judgeFn?: unknown }): boolean {
  return typeof e.checkFn === 'function' || typeof e.judgeFn === 'function';
}

Prevention

When it happens

Trigger: Constructing an Eval, optionally setting .description/.model/.credential, then calling .run(input) or passing it to a runner that triggers build — without ever calling .check() or .judge().

Common situations: Builder chain interrupted by an exception or early return; conditionally setting the scoring function behind a flag that resolved false; copy-paste that dropped the scoring line.

Related errors


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

Appendix: source

Thrown at packages/@n8n/agents/src/sdk/eval.ts:115

	 * Mutually exclusive with `.check()`.
	 */
	judge(fn: JudgeHandlerFn): this {
		if (this.checkFn) {
			throw new Error(`Eval "${this.evalName}": cannot use both .check() and .judge()`);
		}
		this.judgeFn = fn;
		return this;
	}

	/** The eval name. */
	get name(): string {
		return this.evalName;
	}

	/** @internal Build the eval into a runnable form. */
	protected build(): BuiltEval {
		if (!this.checkFn && !this.judgeFn) {
			throw new Error(`Eval "${this.evalName}" requires either .check() or .judge()`);
		}

		if (this.judgeFn && !this.modelId) {
			throw new Error(`Eval "${this.evalName}" uses .judge() but no .model() was set`);
		}

		const name = this.evalName;
		const desc = this.desc;

		if (this.checkFn) {
			const checkFn = this.checkFn;
			return {
				name,
				description: desc,
				evalType: 'check' as const,
				modelId: this.modelId ?? null,
				credentialName: this.credentialName ?? null,
				_run: async (input: EvalInput) => await checkFn(input),

View on GitHub (pinned to 5ac6606e81)