Skip to content

Error codes

Nola diagnostics use stable codes:

  • NOLA1xxx: parse errors, reported by the editor, nola check and nola build.
  • NOLA2xxx: compile errors, reported by the same tools.
  • NOLA3xxx: runtime errors from the runtime, loader or a provider.
  • NOLA4xxx: build-time errors from bundler plugins.

Each code has a permanent link, such as /docs/reference/error-codes/#nola2001. Codes marked not raised are reserved or retired and will not be reused.

Parse errors (NOLA1xxx)

(the parser’s own message)

Cause: a syntax error without a more specific Nola code, such as an unbalanced brace or stray token. Fix: check the location in the message and correct the TypeScript or JavaScript syntax.

ask is a reserved word in .tsi files and cannot be used as an identifier.

Cause: ask used as a variable, function or parameter name. Fix: rename it. ask stays legal as a member or property name (obj.ask).

this Nola construct is reserved for a future Nola version.

Cause: syntax reserved for future use: an infer generator (infer function*), a marker on a class method, a bodiless infer function, or the derive-all call fn(..). See Restrictions for other reserved forms. Fix: use a top-level infer function declaration and write each call-intent argument as an explicit extractor (..`…`: T).

infer on an arrow or function expression produces NOLA1001 instead. It is a keyword only directly before function at statement or export position.

expected a template literal prompt after ...

Cause: .. followed by something other than a backtick instruction. Fix: write ..`instruction`: T.

Not raised — reserved (infer without a following function is reported as NOLA1001).

a marker after a plain function’s name is not valid — only an infer function has that slot, and there it is reserved (NOLA1019).

Cause: a backtick literal between a plain function’s name and its parameter list. Fix: drop it; a plain function takes no marker. For an infer function, write the text as a context statement in the body (Context statements).

expected a model name after ask with — for a dynamic model use .withModel(...) on the intent.

Cause: a string literal, a number or a parenthesized expression after with — anything that is not a bare identifier. (A dotted name such as provider.fast is parsed as the identifier provider followed by a stray .fast, so it reports NOLA1005/NOLA1001 rather than this code.) Fix: name the model in nola.config.ts and use that bare identifier; for a dynamic choice use .withModel(…).

// not-checked — WRONG
export infer function summarize(.text: string, useFast: boolean) {
const a = ask with "fast" ..`a rough summary`: string;
const b = ask with (useFast ? "fast" : "careful") ..`a rough summary`: string;
return { a, b };
}
// summarize.tsi — RIGHT
export infer function summarize(.text: string, useFast: boolean) {
const c = ask with fast `a rough summary`: string;
const d = ask (..`a rough summary`: string).withModel(useFast ? "fast" : "careful");
return { c, d };
}

. context parameters are only allowed on infer function parameters.

Cause: a .name parameter on a plain function, or a .name binding outside an infer body / the module body — there is no inference context to put the value in. Fix: drop the dot (it is an ordinary argument or variable), or make the function an infer function.

// not-checked — WRONG
function summarize(.text: string) {
return text.slice(0, 10);
}
// summarize-right.tsi — RIGHT
export infer function summarize(.text: string) {
return ask `a one-sentence summary`: string;
}

. on this parameter form is reserved for a future Nola version — use a plain identifier parameter.

Cause: . on a destructuring pattern or a parameter with a default value. Fix: use a plain identifier parameter (.issue: Issue) and destructure or default inside the body.

incomplete . context parameter — write .name.

Cause: a . with no parameter name after it (usually mid-typing). Fix: finish the name.

contextual parameters take one dot — write .name (.. is the extractor sigil).

Cause: ..name on a parameter. Fix: use one dot: .name. Two dots introduce an extractor.

var .x is reserved — a contextual binding is const .x or let .x.

Cause: the dot on a var declaration — var hoists to undefined, which no ask should see. Fix: write const .x or let .x — see Contextual bindings.

the implied extractor needs whitespace before its template — write ask `…` (ask`…` reads as a tagged template).

Cause: a template literal glued to ask (or to the model name after ask with) — ask`the user's name`: string. Without the space it reads as a tagged template call on ask, which is not what runs. Fix: put at least one space (any whitespace) between ask and the backtick, or write the explicit sigil — ask ..`…`: T. See Extractors.

expected a type after the extractor’s : on the same line — write `…`: T.

Cause: a colon after an extractor’s closing backtick starts its type, and nothing type-like follows it on that line — ask `the user id`: at the end of a line, or ask `the user id`: ;. The type has to start on the colon’s line because TypeScript’s type grammar reads across line breaks: otherwise an unfinished : above an existing statement would swallow that statement’s first expression as a type name. Fix: write the type right after the colon — ask `the user id`: number — or use the angle-bracket spelling, ask `the user id`<number>. See Extractors.

the marker after an infer function’s name is reserved for a future Nola version — write the instruction as a context statement in the body: ….

Cause: a backtick literal between an infer function’s name and its parameter list — infer function f`…`(…). That slot no longer carries the instruction. Fix: write the text as a context statement in the body (Context statements):

triage.tsi
export infer function triage(.ticket: string) {
`triage the ticket like a support lead`
return ask `the severity: low, medium or high`: "low" | "medium" | "high";
}

a value in a context statement is a name, a call or a bracketed expression, followed by text or the end of the statement — wrap anything else in parentheses, or assign it to a local first (await, ask and this cannot be values).

Cause: after the text of a context statement, a token that cannot continue it on the same line: an operator or an arrow after a value (`total` a + b, `map` x => x), or typeof, void, await, ask, this, .., function or class where a value would start. Also a value that ends with a backtick, which takes the text after it for a tagged template: `x` foo<string> `more`, `x` new Foo `more`. Fix: parenthesize the expression — `total` (a + b), `x` (foo<string>) `more`, `x` (new Foo) `more` — or put it in a ${} hole of the text. await, ask and this cannot go in a value even in parentheses: a parenthesized ask is NOLA2010, and a parenthesized await or this is a TypeScript error (TS1308, TS2683), because a context statement is read at each ask, outside the async body, in a hoisted function of its own. Compute the value first, assign it to a local and write its name: const user = this.user;, then `Page` user.

// not-checked — WRONG
export infer function total(.items: string[], a: number, b: number) {
`total` a + b `items`; // an operator after a value
return ask `a summary`: string;
}
// total.tsi — RIGHT
export infer function total(.items: string[], a: number, b: number) {
`total` (a + b) `items`;
return ask `a summary`: string;
}

Compile errors (NOLA2xxx)

ask is only allowed directly inside an infer function body or the module body.

Cause: ask inside a plain function or a nested closure — even one written inside an infer function — or in a class field initializer / static block, where await is illegal. Fix: ask directly in the infer body or at the top level of the module; from plain TS, await the infer function instead. Constructing an extractor anywhere is fine — only resolving it is restricted.

// not-checked — WRONG
const g = async () => ask `the kind`: string; // plain closure
export infer function f(.t: string) {
const h = () => ask `the kind`: string; // nested closure
return h();
}
// kind.tsi — RIGHT
export infer function f(.t: string) {
return ask `the kind`: string;
}

unsupported type for intent schema: …

Cause: an extractor’s type resolves to a type the checker-backed derivation cannot turn into a JSON Schema — Map, Set, Promise, RegExp, functions, a generic used without arguments — or a member of it does; the message names the member and the type. Fix: ask for a JSON-shaped type and convert afterwards in plain TypeScript. See Schema derivation.

// not-checked — WRONG
export infer function tally(.doc: string) {
return ask `counts per label`: Map<string, number>;
}
// tally.tsi — RIGHT
export infer function tally(.doc: string) {
const counts = ask `counts per label`: { label: string; count: number }[];
return new Map(counts.map((c) => [c.label, c.count]));
}

infer functions must be declared at module top level.

Cause: an infer function declared inside another function or block. Fix: move it to module level.

an extractor used as a call-intent argument must have an explicit type — write ..…: T here.

Cause: a call-intent slot written as ..`…` with no type. (A bare template with no type, createTicket(`a short ticket title`, 2), is a plain string argument, not a slot: the call stays an ordinary call.) Fix: give every slot a type, `…`: T (or `…`<T>); the .. is implied in a call’s argument list.

// not-checked — WRONG
declare function createTicket(title: string, priority: number): Promise<string>;
export infer function fileTicket(.request: string) {
return ask createTicket(..`a short ticket title`, 2);
}
// file-ticket.tsi — RIGHT
declare function createTicket(title: string, priority: number): Promise<string>;
export infer function fileTicket(.request: string) {
return ask createTicket(`a short ticket title`: string, 2);
}

“./x.tsi” names neither a Nola file nor a TypeScript module

Cause: a relative .tsi import — written by you in a .ts file, or generated for a type a .tsi imports — found no x.tsi, x.ts or x.d.ts next to the importer. Fix: check the specifier. ./models.tsi means the Nola file models.tsi when it exists, otherwise the view of models.ts. See TypeScript interop.

contextual parameter ‘…’ has a type that cannot be derived for inference: …. Set compiler.underivableContextType to “prune” or “omit” in nola.config.ts to allow it.

Cause: a .param whose resolved type is not derivable (same rules as NOLA2002); its value would have to be serialized into the prompt. The message names the offending member. Fix: pass a JSON-shaped view, keep the exotic value as a plain parameter, or relax compiler.underivableContextType — see Contextual parameters.

// not-checked — WRONG
export infer function topLabel(.index: Map<string, number>) {
return ask `the label with the highest count`: string;
}
// top-label.tsi — RIGHT
export infer function topLabel(.index: { label: string; count: number }[]) {
return ask `the label with the highest count`: string;
}

Nola constructs are not allowed inside a context statement or an instruction literal’s hole; a hinted call intent used as a value must be parenthesized.

Cause: ..`…` or ask inside a context statement — as a parenthesized value, nested in a bracketed value or in a ${} hole of its text — or inside a ${} hole of a call hint. A context statement is read at each ask with no frame of its own, and a call hint is re-emitted from source. A call intent is a legal value, but a hinted one, fn`hint`(…), written bare is read as the value fn, then the text hint, then a parenthesized group: its ..`…`: T argument is the bare extractor this code reports (written without the dots, (`…`: T), the same group is a syntax error at the colon, since a parenthesized group has no slots). Written directly after the text, a bare ask or .. is NOLA1020 instead. Fix: compute the value first and interpolate the result, or move the ask into the function body. To use a call intent with a hint as a value, parenthesize it or write it in a ${} hole; a plain call with an extractor argument needs no parentheses.

// not-checked — WRONG
declare function escalate(team: string): void;
export infer function route(.ticket: string) {
`If the ticket is an outage,` escalate`page the on-call engineer`(..`the team to page`: string)
return ask `the label for the ticket`: string;
}
// route.tsi — RIGHT
declare function escalate(team: string): void;
export infer function route(.ticket: string) {
`If the ticket is an outage,` (escalate`page the on-call engineer`(`the team to page`: string))
return ask `the label for the ticket`: string;
}

exported type ‘…’ would also become a value, but a value named ‘…’ is already declared in this file — rename one of them

Cause: every exported type alias and interface in a .tsi also becomes a runtime value under the same name (Types as values), and this file already declares a const, let, var, function, class or enum with that name. Fix: rename the type or the value.

property ‘age’ of Signup: @minLength applies to strings, not to number

Cause: a JSDoc constraint tag (Constraints) does not fit — the keyword is for another kind of type, its value is missing or not a number, the @format name is unknown, the @pattern is not a valid regular expression, or the tag is given twice. Raised at the type for every kind of site (exported type, extractor, contextual parameter); the contextual-parameter policy does not apply, and prune mode does not drop the member. Fix: move the tag to a member of the right kind, or correct its value.

a typed template literal is an extractor only directly after ask or in a call’s argument list; write ..`…`<T> here.

Cause: a template literal with type arguments — const i = `x`<string> — somewhere other than directly after ask or in a call’s slots (a direct argument, or a value nested in object/array literals there). The two dots are implied only in those two places; elsewhere a bare template is an ordinary string, so an extractor there needs its sigil (Two spellings, one construct). The colon spelling has no such code: const i = `x`: string is an ordinary syntax error. Fix: write ..`x`: T (or ..`x`<T>) — for a stored intent, or an extractor under a ternary, a spread or new.

Dept: Choice needs 2 to 255 labels, got 1

Cause: the criteria of a decision type are malformed — a Choice argument that is not a label union or a { label: description } type literal of string literals and null, fewer than 2 or more than 255 labels; a Scale argument that is not a tuple of 2 to 10 string literals; a Prob argument that is not exactly { true: "…"; false: "…" }. Raised at the type for every kind of site (exported type, extractor, contextual parameter); the contextual-parameter policy does not apply, and prune mode does not drop the member. Fix: write the criteria as literals: Choice<{ billing: "Payments"; sales: null }>, Scale<["Calm", "Angry"]>, Prob<{ true: "…"; false: "…" }>.

a context statement is only legal in a scope body — directly in an infer function or at module level. · a context statement cannot be the unbraced body of an if, a loop or a label — wrap it in braces.

Cause: a bare template literal statement inside a plain function, a callback (even one written inside an infer function), a class static block or a namespace body, or as the unbraced body of an if, else, loop or label (if (x) `text`;). No ask can run there, so nothing could carry the text to the model. Fix: move the statement directly into the infer function’s body or the module body, put braces around an unbraced body, or make the text a plain string if it was never meant for the model.

// not-checked — WRONG
export infer function go(.items: string[], urgent: boolean) {
items.forEach((it) => {
`note` it; // a callback: no ask can carry it
});
if (urgent) `Answer quickly.`; // an unbraced body
return ask `a summary`: string;
}
// go.tsi — RIGHT
export infer function go(.items: string[], urgent: boolean) {
`items:` items;
if (urgent) {
`Answer quickly.`
return ask `a short summary`: string;
}
return ask `a summary`: string;
}

Runtime errors (NOLA3xxx)

This module was compiled for Nola emit contract N, but @nola-lang/runtime (at …) provides contract M. …

Cause: lowered code and the installed runtime disagree on the emit contract — a stale build or mismatched package versions. Fix: the message says which: rebuild with the current nola-lang, or update @nola-lang/runtime. Keep the lockstep packages on one version.

Two incompatible copies of @nola-lang/runtime are loaded: … Run npm dedupe, or align your nola-lang versions so a single runtime is installed.

Cause: two runtimes with different emit contracts in one process. Fix: npm dedupe; align every Nola package to the same version.

(one of several config messages, e.g.) a model map must include a default entry. · unknown config key x — allowed keys: … · Nola configuration is frozen after the first ask — call nolaRuntime.reset() before reconfiguring. · fallback([]) needs at least one model. · fallback() cannot wrap nola — the platform model can only be the root: it manages its own resilience (nola.infer({ retry }) for transport retry; routing and failover live on the server). record()/replay() remain available.

Cause: invalid config, reconfiguration after the first ask, an empty provider list, or an attempt to wrap the platform model in withRetry, fallback or roundRobin. Fix: follow the file and field named in the message. See Config schema and Providers API.

The related message “No Nola model configured” is a NolaConfigError without a code. It means no config was loaded. Use nola run or the loader with a nola.config.ts, or call nolaRuntime.configure(…) before the first ask.

forceModel “x” does not name a configured model — configured: … · .withModel() “x” does not name a configured model — configured: …

Cause: forceModel, ask with <name> or .withModel("name") names a key that is not in the model map. When the platform serves inference — model: "nola" — an ask-site name that matches no key is not an error: it is sent to the platform as an inference profile instead, so this code then fires only for forceModel. Fix: add the model to the map, fix the name, or let the platform serve (model: "nola") so free-form names route through it.

plugins is reserved for a future Nola version.

Cause: a plugins key in the config. Fix: remove it.

cache must be an object — write cache: {} or cache: { store: <NolaCacheStore> }. · cache.store is not a NolaCacheStore (need { get(fingerprint), set(fingerprint, value) }).

Cause: a malformed cache section. Cache settings are validated but currently have no effect. Fix: use cache: {} or a store with get and set methods.

replay ledger … cannot be read: … · replay ledger …:N is not valid JSON. · replay ledger …:N is missing fingerprint or response.text.

Cause: the JSONL ledger given to replay(path) is missing, unreadable or malformed. Fix: check the path; re-record the ledger. See Record and replay.

replay ledger … has no entry for fingerprint … — the prompt, schema, or context changed since the ledger was recorded. Re-record it.

Cause: no recorded request matches the current prompt, contextual values, schema, params or inference profile. The provider name alone does not affect the match. Fix: record the changed requests (NOLA_RECORD=1 with the setup in Record and replay), or switch to a live provider.

this type cannot be used in an intent schema: …

Cause: an underivable type reached an ask at run time (a companion module marked it unsupported). Fix: as for NOLA2002 — use a JSON-shaped type.

extract/call intents carry no construction scope — only ask supplies their frame.

Cause: a raw extractor or call intent was awaited or run outside ask, often from plain TypeScript. Fix: resolve it with ask inside an infer function or at module level in a .tsi file. From plain TypeScript, await the infer function’s result.

// not-checked — WRONG: nothing supplies the inference context
import { nameIntent } from "./person.tsi";
const name = await nameIntent;
// person.tsi — RIGHT
export const nameIntent = ..`the user's full name`: string;
export infer function whoIsIt(.text: string) {
return ask nameIntent;
}

Not raised — reserved.

nola.config.ts cannot import “.tsi” modules (“…” imported from …) — the config is evaluated before the Nola loader registers.

Cause: nola.config.ts (or something it imports) imports a .tsi file. Fix: keep the config graph plain TypeScript.

Nola asks cannot execute in a browser context (server-only in v0). Move this call behind a server boundary (server component, route handler, server action).

Cause: an ask ran where window and document exist. Fix: call the infer function from server code.

the Nola loader needs Node.js module hooks, which Bun/Deno does not run. Run .tsi code on Node: bun run start / npm start (the nola bin runs on Node) or node --import nola-lang/register src/main.ts; bun --bun and bun src/main.ts cannot load .tsi files.

Cause: the loader was started under Bun or Deno. Fix: run on Node (≥ 22.18); any package manager is fine.

value does not match …: …

Cause: parse() on a type value rejected the input; the thrown NolaValidationError carries the structured issues. Fix: validate with validate() when rejection is expected, or fix the input. See Validate and parse.

JSON Schema target ‘…’ is not supported (expected one of ‘draft-2020-12’, ‘draft-07’, ‘openapi-3.0’)

Cause: a Standard JSON Schema consumer asked User["~standard"].jsonSchema.input() / .output() for a dialect Nola does not emit. The spec requires a library to throw rather than hand back the wrong dialect. Fix: pass one of the three supported targets. See Standard Schema.

output property “department” is a Choice; model “openai” cannot answer decision questions — route the ask to a decision model (ask with <name>) or use the plain form (a literal union, or boolean).

Cause: the output type contains Choice, Scale or Prob, but the selected model does not support decision questions. Nola rejects the ask before sending a request because those types require probabilities from a decision model. A fallback or roundRobin group supports decisions if at least one member does; other members are skipped for decision asks. Fix: select a decision model with ask with <name> or .withModel(). If you only need a label or yes/no answer, use a literal union or boolean.

Bundler errors (NOLA4xxx)

.tsi modules are server-only in Nola v0 — this file is being bundled for a browser target. Move the import behind a server boundary (SSR entry, route handler, server action).

Cause: a client bundle imports a .tsi module. Fix: import it from server code only (SSR entry, route handler, server action).

Next: Syntax cheatsheet