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
- Add .check(fn) for deterministic scoring or .judge(fn) plus .model(id) plus .credential(name) for LLM-as-judge.
- Gate eval construction on the condition that selects a scoring mode so the builder always ends with one.
- 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
- Always end an Eval chain with .check or (.model + .credential + .judge).
- Add a test that builds each Eval to surface missing scorers at test time.
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
- duplicate case slug(s) across data/workflows and data/agents
- Eval " ": cannot use both .check() and .judge()
- Eval " " uses .judge() but no .model() was set
- Guardrail " " requires a strategy
- Guardrail " " requires a type
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)