Diagnostics

Every build-time diagnostic code, what it means, and how to act on it.

RunTypes flags type problems while your project builds, each as a short code like VL010 or PJ001. The letters name the feature (VL is createValidateFn, PJ is prepareForJson), and the number marks the position: 001 to 009 are root errors, 010 and up are member-level warnings.

Severity decides what happens next. A Warning tells you something worth knowing (usually an expected drop) and never stops anything. An Error stops the build, and by default it stops every lane, including the dev server and test runs under vitest, not just production builds. That behaviour is the plugin's failOnError option, on by default; set it to false to keep errors as warnings in a program that deliberately contains invalid types. See Configuration.

One error is worth calling out: MKR007. When an import in a file fails to resolve during the type scan, a type used at a marker call can quietly check as any, and functions generated from any would accept everything with no runtime signal that something went wrong. Instead, the build stops and names the file and the unresolved import, so you can fix the path (often a missing file extension) and rebuild.

Warning

A safe, expected drop, and the build keeps going. Anything with no data form (a method, a function-valued property, a symbol key) is left out of the generated function.

Error

The generated function would throw the moment you call it, so the build stops. A whole value has no data form, like a type that resolves to never or a bare symbol. Change the type.

Project configuration

Raised when the project tsconfig the tooling was pointed at cannot be loaded.

CFG001Error
Project tsconfig failed to load ({0}) — the build, the linter, and the CLI all read this config, so nothing can run until it loads.

The tsconfig.json your project named, or the one found next to it, is missing or does not parse. RunTypes reads types through that config, the same one your build uses, so the operation stops instead of guessing with defaults that could resolve your types differently. Fix the tsconfig, or point the tooling at the right file with the plugin or lint tsconfig setting, or the CLI --tsconfig flag.

Full build message
RunTypes derives every type query from your project tsconfig, the same
file your build uses. A tsconfig that was named (or found next to your
project) but is missing or does not parse stops the operation, exactly
like `tsc --project` would, instead of silently falling back to defaults
that could resolve your types differently.

Fix — repair the tsconfig (the message names the first parse problem),
or point the tooling at the right file (the plugin/lint `tsconfig`
setting, or the CLI `--tsconfig` flag).

Markers and call sites

Raised at a marker call, before the build can turn your type into a function.

CTA001Error
`CompTimeArgs<T>` argument must be a literal at the call site, or a `const` whose initializer is itself entirely literal (a same-module or imported `const` both work).
Full build message
The build resolves the argument before running, so it needs to read its
value from the source. Function-call results, property accesses, ternary
expressions, and `let`/`var` bindings can't be evaluated at build time.
Accepted: an inline literal, or a `const` whose initializer is itself
fully literal — including a `const` imported from another module. (An
object `const` must be `as const` so its members stay literal; see CTA004.)

Fix — inline at the call site:
-  const opts = getOpts();
-  const isUser = createValidateFn<User>(undefined, opts);
+  const isUser = createValidateFn<User>(undefined, {mode: 'unsafe'});

Fix — use a const of literals (here or in another module):
  const opts = {mode: 'unsafe'} as const;   // literal initializer ✓
  const isUser = createValidateFn<User>(undefined, opts);
CTA002Error
`CompTimeArgs<T>` literal nesting exceeds the depth cap (16) — refactor to flatten.
Full build message
Deeply nested literal walks are capped at 16 levels to keep the build
predictable. If you hit this, the value is almost certainly not what
you want at compile time — split it across multiple smaller
`CompTimeArgs<T>` arguments, or flatten the nesting.
CTA003Error
`CompTimeArgs<T>` literal contains a forbidden construct ({0}). Only literals and nested literals are allowed.
Full build message
The Go scanner cannot statically evaluate computed property names,
function calls, ternary expressions, or template-string substitutions.
Inside a `CompTimeArgs<T>` literal every node must be a direct literal
(string / number / bigint / boolean / null / undefined / regex / arrow /
object literal / array literal) or a const-traced identifier that
resolves to one.

Spread IS allowed when its operand resolves to a literal container of the
matching kind — a `const`-bound (or imported) object literal for an
object spread, an array literal for an array spread:
  const base = {strict: true};
  const a = {...base, mode: 'unsafe'};        // ok — merges a const fragment

A spread is still rejected when the operand can't be statically merged —
a dynamic value, or a shape mismatch:
  -  const a = {...getDefaults(), mode: 'unsafe'};   // dynamic operand
  -  const a = {...[1, 2], mode: 'unsafe'};          // object spread of an array
CTA004Error
`CompTimeArgs<T>` value comes from a `const` with a widened (non-literal) member ({0}) — declare the const `as const`.
Full build message
A `const` used as a CompTimeArgs / CompTimeFnArgs argument (a whole option
bag, or a builder child) must carry LITERAL value types, so the value the
build reads matches the type TypeScript resolves the call against. Without
`as const`, an object literal's members widen — `{strategy: 'mutate'}`
becomes `{strategy: string}` — which can let the type system select one
function variant while the build injects another.

Whole imported consts now resolve cross-module (like a spread fragment), so
this rule keeps that path sound.

Fix — add `as const`:
-  const preset = {strategy: 'mutate'};
+  const preset = {strategy: 'mutate'} as const;
   createJsonEncoderFn(undefined, preset);
MKR001Warning
`{0}()` is being called at runtime just so the marker can read its return type — side effects, throws, or async work run for nothing.
Full build message
Reflect-form markers (`createValidateFn(value)`, `getRunTypeId(value)`)
invoke their argument expression at runtime; the value is then discarded —
only its inferred type is used.

Fix — use the static form with `ReturnType<>`:
  -  const isUser = createValidateFn({0}());
+  const isUser = createValidateFn<ReturnType<typeof {0}>>();

Fix — pass an existing value of the desired type:
  const existingUser: User = ...;
  const isUser = getRunTypeId(existingUser);
MKR003Error
Marker call is inside a generic function — the type argument is unresolved, so no id can be computed at build time.
Full build message
The build can only compute an id for a concrete type (`User`,
`{name: string}`, etc.). A type parameter like `T` is abstract — it
takes a different value at each call site of the surrounding function,
so a single id can't represent it.

Fix — inline the marker at each concrete call site:
  function isUser(value: unknown) {
    return createValidateFn<User>()(value);
  }

Fix — accept a pre-computed id from the caller:
  function makeChecker<T>(id: InjectRunTypeId<T>) {
    return createValidateFn<T>(id);
  }
  const isUser = makeChecker<User>(getRunTypeId<User>());
MKR004Warning
`noLiterals: true` has no effect here — the type argument doesn't resolve to literal values.
Full build message
The `noLiterals` validate option skips the exact-value check that literal
types (`'admin'`, `42`, `true`) compile to. This call's type argument
resolves to a non-literal type, so there is no literal check to skip and
the option is a silent no-op.

Fix — drop the option:
-  const isRole = createValidateFn<string>({noLiterals: true});
+  const isRole = createValidateFn<string>();

Or, if you meant to relax a literal union, point the option at the type
that actually carries the literals:
  const isRole = createValidateFn<'admin' | 'user'>({noLiterals: true});
MKR005Warning
`noIsArrayCheck: true` has no effect here — the type argument is not an array type.
Full build message
The `noIsArrayCheck` validate option skips the `Array.isArray` guard that
array types compile to. This call's type argument resolves to a non-array
type, so there is no guard to skip and the option is a silent no-op.

Fix — drop the option:
-  const isUser = createValidateFn<User>({noIsArrayCheck: true});
+  const isUser = createValidateFn<User>();

Or point it at the array type you meant:
  const isUsers = createValidateFn<User[]>({noIsArrayCheck: true});
MKR006Error
`InjectTypeFnArgs` names the function family `{0}` more than once — remove the duplicate key.

An InjectTypeFnArgs marker lists each function family it needs once, in order. Naming the same family twice injects a second identical handle that nothing reads, so it is almost always a copy-paste slip and the build stops. List each family at most once.

import type {InjectTypeFnArgs} from '@ts-runtypes/core';
type Handler = (ctx: unknown, ...rest: any[]) => unknown;
function route<H extends Handler>(handler: H, fns?: InjectTypeFnArgs<Parameters<H>, 'verr', 'jsonDecoder', 'verr'>) {
  return {handler, fns};
}
export const lenRoute = route((ctx: unknown, name: string) => name.length);
function route<H extends Handler>(
  handler: H,
  fns?: InjectTypeFnArgs<Parameters<H>, 'verr', 'jsonDecoder', 'jsonEncoder'>,
) {
  return {handler, fns};
}
Full build message
An `InjectTypeFnArgs<T, …>` marker names each function family it needs for
`T` once, in declaration order; the build injects one entry-module tuple
per name and the wrapper forwards each to its factory. Naming a family
twice would inject a redundant identical tuple with no consumer, so it is
almost always a copy-paste slip and the build stops.

Fix — name each family at most once:
-  id?: InjectTypeFnArgs<T, 'verr', 'jsonDecoder', 'verr'>;
+  id?: InjectTypeFnArgs<T, 'verr', 'jsonDecoder', 'jsonEncoder'>;
MKR007Error
Marker type resolved to `any` because this file has an unresolved import (`{0}`) — the generated functions would silently accept anything.
Full build message
TypeScript could not resolve the import, so the type it should have
provided checked as `any` at this marker call. A validator over `any` is
the always-true identity, a mock over `any` is `undefined`, and encoders
pass values through untouched — with no runtime signal that anything is
wrong. This usually means the build tool and the type scanner resolve
modules differently (e.g. an extensionless relative import under
`moduleResolution: NodeNext`, a missing dependency, or a `paths` alias the
scan tsconfig doesn't declare).

Fix — make the import resolve for the type scanner:
-  import {User} from './user.runtype';
+  import {User} from './user.runtype.ts';

Or align the tsconfig the plugin scans with the one your bundler uses.
If the `any` is genuinely intentional, write the marker over an alias
declared in resolving code (e.g. `type Loose = any`) in a file with no
failing imports.
MKR008Error
This type is too deeply nested to reflect — computing its structural id hit the recursion depth cap, so the build stops here instead of crashing.
Full build message
The build computes a structural id by walking the type, and the walk is
capped at a depth far beyond any realistic shape. Hitting the cap with no
single recurring type on the path means literally written (or generated)
nesting hundreds of levels deep.

Fix — reflect a concrete, bounded projection of the type (e.g. the element
or data type you actually send), or restructure the recursion so the same
named type recurs by reference (a plain recursive interface is fine).
MKR009Error
Type `{0}` re-instantiates itself with fresh type arguments at every level (a self-instantiating generic), so its structural id never resolves. Reflect a monomorphic shape instead.
Full build message
A generic method's own type parameters (the `U` in `map<U>(fn: (x: T) => U):
Iter<U>`) are bound at each CALL of the method, so they can never be resolved
while reflecting the containing type — and when such a method returns a fresh
instantiation of its own container, the type graph grows a new level forever.
Renaming the type parameters does not resolve them; the fix is a monomorphic
(fully resolved) recursive shape, which closes by reference:

-  interface Iter<T> { map<U>(fn: (x: T) => U): Iter<U> }
+  interface NumberIter { map(fn: (x: string) => number): NumberIter }

Ordinary generics are unaffected: instantiated types (Map<string, User>, a
concrete Iter<string>'s data members) and generic methods that do not
re-instantiate their container reflect fine. Validators also drop methods
entirely (methods aren't data), so reflecting just the data shape usually
sidesteps the problem.
MKR010Error
Type argument contains the unresolved type parameter `{0}` — a generic must be fully resolved at the marker call, so no id can be computed. See Related for where `{0}` is declared.
Full build message
The build can only compute an id for a fully concrete type. `{0}` is a type
parameter of the surrounding generic — it takes a different type at each call
site, so a single build-time id would alias every instantiation onto one
(wrong) shape. A parameter DEFAULT does not help here: defaults resolve where
a caller omits the argument, never inside the generic's own body.

Fix — resolve the generic before reflecting it:
  interface Box<T> { value: T }
  type BoxString = Box<string>;
  const isBoxString = createValidateFn<BoxString>();   // resolved — ok

Fix — or accept a pre-computed id from the caller and inline the marker at
each concrete call site (same patterns as MKR003):
  function makeChecker<T>(id: InjectRunTypeId<T>) {
    return createValidateFn<T>(id);
  }
  const isBox = makeChecker<Box<string>>(getRunTypeId<Box<string>>());

Generic METHODS on a concrete type (`find<T>(query: string): T[]`) are
unaffected — their own type parameters are bound per call of the method and
methods aren't data.
MKR011Error
Generic type `{0}` is used without its required type argument(s) — parameter `{1}` has no default, so the type cannot resolve to an id. See Related for where `{1}` is declared.
Full build message
TypeScript itself rejects this usage (TS2314), but dev-server builds don't
run the type checker, so the scan reads the written type arguments and stops
the build here instead of silently reflecting `any` (a validator over `any`
accepts everything).

Fix — pass the missing type argument:
-  const isA = createValidateFn<A>();
+  const isA = createValidateFn<A<string>>();

Fix — or give the parameter a default, which the compiler resolves at every
bare use site:
-  interface A<S extends string> { a: S }
+  interface A<S extends string = string> { a: S }
   const isA = createValidateFn<A>();   // now resolves to A<string>
PFN001Error
`PureFunction<F>` argument must be an INLINE arrow or function expression.
Full build message
The build extracts and AOT-compiles the function body, so it must see the
literal inline at the call site. A named reference — even a module-private
`const f = …` or `function f(){}` — is not accepted, because the literal
must have no handle anything else can reach; the compiled copy is then the
only one that can run. (An imported or exported literal is rejected as PFN002.)

Fix — inline the function at the call site:
-  const validate = (v: unknown) => typeof v === 'string';
-  registerValidator(validate);
+  registerValidator((v: unknown) => typeof v === 'string');
PFN002Error
`PureFunction<F>` literal must not be imported or exported — the compiled copy must be the only one that can run.
Full build message
The build extracts and AOT-compiles the function body, and the compiled
copy is the single source of truth. If the original literal stays reachable
as a value — imported from another module, or exported so another module can
import it — a caller could invoke the un-compiled function and diverge from
the compiled behaviour.

Under the literal-only rule a named binding isn't allowed at all (see PFN001),
so the fix is to inline the function at the call site:
-  import {validate} from './validators';   // imported — rejected
-  export const validate = (v) => …;        // exported — rejected
+  registerValidator((v: unknown) => typeof v === 'string');   // inline — ok
TMP001Error
Temporal type `{0}` resolved to `any` — the Temporal lib isn't in your tsconfig `lib`, so the generated validator would accept any value.
Full build message
ts-runtypes reads types through TypeScript's lib definitions, so it
can only validate `Temporal.*` types when the Temporal namespace is loaded.
With the lib missing, `{0}` silently degrades to `any` and the validator
becomes a no-op that accepts everything — almost never what you intended.

Fix — add "ESNext.Temporal" to your tsconfig:
  {
    "compilerOptions": {
      "lib": ["ES2023", "ESNext.Temporal"]
    }
  }

Validation

From createValidateFn and createGetValidationErrorsFn.

VE001Error
Type `{0}` can never be validated — the generated function will always fail.

Same case as VL001, from createGetValidationErrorsFn. The type is a built-in that carries runtime state and cannot survive a JSON round trip. Report errors against a plain shape, or convert the value first.

import {createGetValidationErrorsFn} from '@ts-runtypes/core';
export const errorsOf = createGetValidationErrorsFn<Uint8Array>();
Full build message
Built-in classes like `Map`, `Set`, `WeakMap`, `WeakSet`, `Int8Array`,
`Uint8Array`, `Buffer`, and `Promise` carry runtime state that doesn't
survive a JSON or binary round-trip. Their instance identity is lost the
moment they're serialised.

Fix — convert to a plain object/array before serialising:
  // for Map<K, V>:
  const data = Object.fromEntries(yourMap);
  // for Set<T>:
  const data = [...yourSet];
  // for typed arrays:
  const data = Array.from(yourBuffer);

Fix — change the field type to a serialisable shape:
  interface User {
-   tags: Set<string>;
+   tags: string[];
  }
VE002Error
Type `{0}` can never be validated — the generated function will always fail.

Same case as VL002, from createGetValidationErrorsFn. The type is a bare symbol, which cannot round trip. Use a string union instead.

import {createGetValidationErrorsFn} from '@ts-runtypes/core';
export const errorsOf = createGetValidationErrorsFn<symbol>();
Full build message
Every `symbol` value carries a unique runtime identity (`Symbol() !==
Symbol()` even with the same description). That identity disappears the
moment it's serialised, and two symbols can't be compared across realms,
workers, or process boundaries. A validator that asserts "this is a
symbol" gives a false sense of safety — the value can't actually
round-trip.

Fix — use a stable string key (often a literal union):
  -  type Status = symbol;
+  type Status = 'pending' | 'active' | 'done';
VE010Warning
Property `{0}` is a function — `validationErrors` does not handle function values, so this property is silently not checked.

Same case as VL010, from createGetValidationErrorsFn. A function-valued property carries no data and is left out of the report.

Full build message
`validationErrors` works on JSON-shaped data; functions don't survive JSON, so
the emitter drops them. The rest of the object's behaviour is unaffected.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If you need a stricter checker that fails on
missing/extra function-typed members, watch the project roadmap.
VE011Warning
Method `{0}` is silently not checked by `validationErrors` — methods aren't data.

Same case as VL011, from createGetValidationErrorsFn. A method or function-typed property is behavior, not data, so it is left out of the report.

import {createGetValidationErrorsFn} from '@ts-runtypes/core';
interface User { name: string; greet(): string; }
export const errorsOf = createGetValidationErrorsFn<User>();
Full build message
Class and object methods aren't part of the serialisable shape, so
`validationErrors` excludes them. The rest of the type still works.

If you wanted the method's return value validated/serialised, expose it
as a data property instead.
VE012Warning
Static member `{0}` is silently not checked by `validationErrors` — statics aren't part of instance data.

Same case as VL012, from createGetValidationErrorsFn. Static members are not part of instance data, so they are left out.

import {createGetValidationErrorsFn} from '@ts-runtypes/core';
class Config { static version = 1; name = ''; }
export const errorsOf = createGetValidationErrorsFn<Config>();
Full build message
Class static members live on the class, not on individual instances.
`validationErrors` operates on instance shape, so statics are excluded.
VE013Warning
Symbol-keyed property `{0}` is silently not checked by `validationErrors` — symbol keys aren't JSON-representable.

Same case as VL013, from createGetValidationErrorsFn. Symbol keys are not JSON-representable, so the property is left out.

Full build message
JSON only supports string keys; symbol-keyed properties are dropped
from the serialised form. `validationErrors` follows the same rule.

Fix — use a string key:
  -  [Symbol.for('id')]: string;
+  id: string;
VE015Warning
Property `{0}` has a non-serialisable value type (symbol, Promise, or a non-serialisable built-in) — `validationErrors` drops it, so this property is silently not checked.

Same case as VL015, from createGetValidationErrorsFn. A property whose value has no data form is left out of the report.

import {createGetValidationErrorsFn} from '@ts-runtypes/core';
interface Box { id: symbol; name: string; }
export const errorsOf = createGetValidationErrorsFn<Box>();
Full build message
`validationErrors` works on JSON-shaped data. A property whose value is a symbol,
a Promise, or a non-serialisable built-in (typed array, ArrayBuffer, …) carries
no JSON-shaped value, so it is dropped: `DataOnly<{ {0}: symbol }>` is `{}`.
The rest of the object's behaviour is unaffected.

Note the difference from a property that is only STRUCTURALLY unserialisable —
`{0}: symbol[]` or `{0}: Map<string, symbol>` — which CANNOT be safely
dropped (DataOnly keeps it as `never[]`): there `validationErrors` throws at build
time instead.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md.
VE020Warning
`validationErrors` on `any` / `unknown` always returns an empty error array — nothing is checked.

Same idea as VL021, from createGetValidationErrorsFn. On any or unknown there is nothing to compare against, so the report is always empty. Narrow the type to the shape you expect.

import {createGetValidationErrorsFn} from '@ts-runtypes/core';
export const errorsOf = createGetValidationErrorsFn<unknown>();
Full build message
Same reason as VL021: `any` and `unknown` describe "anything", so the
checker has no structure to compare against. The returned error array
will always be empty.

Fix — narrow the type to the actual shape you expect:
  -  const errors = createGetValidationErrorsFn<unknown>()(value);
+  const errors = createGetValidationErrorsFn<User>()(value);
VL001Error
Type `{0}` can never be validated — the generated function will always fail.

The type you validate is a built-in that carries runtime state, like a WeakMap, a WeakSet, or a typed array such as Uint8Array. None of these survive a JSON round trip, so a guard that passed for one would claim a safety it cannot deliver. Validate a plain shape, or convert the value before you validate it.

import {createValidateFn} from '@ts-runtypes/core';
export const isData = createValidateFn<Uint8Array>();
const bytes = Array.from(myUint8Array);
const isData = createValidateFn<number[]>();
Full build message
Built-in classes like `Map`, `Set`, `WeakMap`, `WeakSet`, `Int8Array`,
`Uint8Array`, `Buffer`, and `Promise` carry runtime state that doesn't
survive a JSON or binary round-trip. Their instance identity is lost the
moment they're serialised.

Fix — convert to a plain object/array before serialising:
  // for Map<K, V>:
  const data = Object.fromEntries(yourMap);
  // for Set<T>:
  const data = [...yourSet];
  // for typed arrays:
  const data = Array.from(yourBuffer);

Fix — change the field type to a serialisable shape:
  interface User {
-   tags: Set<string>;
+   tags: string[];
  }
VL002Error
Type `{0}` can never be validated — the generated function will always fail.

The type is a bare symbol. Every symbol has its own runtime identity, so it cannot round trip across a network or a process boundary. Use a stable string union instead.

import {createValidateFn} from '@ts-runtypes/core';
export const isData = createValidateFn<symbol>();
type Status = 'pending' | 'active' | 'done';
Full build message
Every `symbol` value carries a unique runtime identity (`Symbol() !==
Symbol()` even with the same description). That identity disappears the
moment it's serialised, and two symbols can't be compared across realms,
workers, or process boundaries. A validator that asserts "this is a
symbol" gives a false sense of safety — the value can't actually
round-trip.

Fix — use a stable string key (often a literal union):
  -  type Status = symbol;
+  type Status = 'pending' | 'active' | 'done';
VL010Warning
Property `{0}` is a function — `validate` does not handle function values, so this property is silently not validated.

A function-valued property carries no data, so it is left out of the validated shape. The surrounding data properties are still checked. Drop the property, or replace it with the data it would produce.

Full build message
`validate` works on JSON-shaped data; functions don't survive JSON, so
the emitter drops them. The rest of the object's behaviour is unaffected.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If you need a stricter checker that fails on
missing/extra function-typed members, watch the project roadmap.
VL011Warning
Method `{0}` is silently not validated by `validate` — methods aren't data.

A function-valued member, written as a method like greet(): string or as a function-typed property like onClick: () => void, is behavior, not data, so it is left out of the validated shape. Expose the data you need as a plain property instead.

import {createValidateFn} from '@ts-runtypes/core';
interface User { name: string; greet(): string; }
export const isUser = createValidateFn<User>();
Full build message
Class and object methods aren't part of the serialisable shape, so
`validate` excludes them. The rest of the type still works.

If you wanted the method's return value validated/serialised, expose it
as a data property instead.
VL012Warning
Static member `{0}` is silently not validated by `validate` — statics aren't part of instance data.

Static members live on the class, not on an instance. Validation works on instance shape, so statics are left out.

import {createValidateFn} from '@ts-runtypes/core';
class Config { static version = 1; name = ''; }
export const isConfig = createValidateFn<Config>();
Full build message
Class static members live on the class, not on individual instances.
`validate` operates on instance shape, so statics are excluded.
VL013Warning
Symbol-keyed property `{0}` is silently not validated by `validate` — symbol keys aren't JSON-representable.

JSON has string keys only, so a symbol-keyed property has nowhere to land in the serialized form. Use a string key if the property is real data.

interface Item {
  id: string; // instead of [Symbol.for('id')]: string
}
Full build message
JSON only supports string keys; symbol-keyed properties are dropped
from the serialised form. `validate` follows the same rule.

Fix — use a string key:
  -  [Symbol.for('id')]: string;
+  id: string;
VL014Warning
Union member(s) of type `{0}` can't be represented as data — `validate` drops them, so the union is validated as its remaining members.

A union is validated as the members that have a data form. Date | symbol validates as Date. If every member has no data form the projection is never, and validation throws at build time instead.

import {createValidateFn} from '@ts-runtypes/core';
export const isData = createValidateFn<Date | symbol>();
Full build message
A union projects to its serialisable members only: `DataOnly<Date | symbol>`
is `Date`. The dropped member(s) ({0}) carry no JSON-shaped value (symbol,
function, Promise, or a non-serialisable built-in like `Map` / `Set` /
typed arrays), so `validate` validated only the members that remain.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If EVERY member of the union is non-serialisable the
projection is `never`, and `validate` throws at build time instead.
VL015Warning
Property `{0}` has a non-serialisable value type (symbol, Promise, or a non-serialisable built-in) — `validate` drops it, so this property is silently not validated.

A property whose value is a symbol, a Promise, or a non-serializable built-in has no data form, so { id: symbol } validates as {}. A value that is only structurally unserializable, like symbol[] or Map<string, symbol>, cannot be dropped without changing the shape, so that case throws at build time instead.

import {createValidateFn} from '@ts-runtypes/core';
interface Box { id: symbol; name: string; }
export const isBox = createValidateFn<Box>();
Full build message
`validate` works on JSON-shaped data. A property whose value is a symbol,
a Promise, or a non-serialisable built-in (typed array, ArrayBuffer, …) carries
no JSON-shaped value, so it is dropped: `DataOnly<{ {0}: symbol }>` is `{}`.
The rest of the object's behaviour is unaffected.

Note the difference from a property that is only STRUCTURALLY unserialisable —
`{0}: symbol[]` or `{0}: Map<string, symbol>` — which CANNOT be safely
dropped (DataOnly keeps it as `never[]`): there `validate` throws at build
time instead.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md.
VL021Warning
`validate` on `any` / `unknown` always returns true — the validator accepts every value.

any and unknown describe anything, so a structural check has nothing to compare against. The guard is always true. Narrow the type to the shape you expect.

import {createValidateFn} from '@ts-runtypes/core';
export const isAnything = createValidateFn<unknown>();
const isUser = createValidateFn<User>(); // instead of <unknown>
Full build message
`any` and `unknown` describe "anything", so a structural validator has
nothing to check. The resulting function passes for every input —
including the ones you probably wanted to reject.

Fix — narrow the type to the actual shape you expect:
  -  const isUser = createValidateFn<unknown>();
+  const isUser = createValidateFn<User>();

Serialization

From the JSON and binary families, plus how classes are handled.

CLS001Warning
class `{0}` is serialized structurally; register it via `registerClassSerializer({0}, { deserialize })` to round-trip a real instance.
Full build message
By default a user class is serialized by its declared properties and
decoded back to a prototype-less plain object — `instanceof {0}` is
false on the decoded value, and any class methods / getters are gone.
This is fine when you only care about the data.

To round-trip a real `{0}` instance, register it once, passing the class
itself (not a name string):
  import {registerClassSerializer} from '@ts-runtypes/core';

  // zero-arg constructor: nothing else needed
  registerClassSerializer({0});

  // non-empty constructor: only `deserialize` is required
  registerClassSerializer({0}, {
    deserialize: (data) => new {0}(/* rebuild from data */),
  });

`serialize` is optional (default: structural, same as any interface);
`deserialize` is optional for a zero-arg class (default:
`Object.assign(new {0}(), data)`). The same registration is used by the
JSON and binary families. `validate` / `getValidationErrors` are
unaffected — they always validate structurally.
FB001Error
Type `{0}` can never be deserialised from binary — the generated function will always fail.
Full build message
`never` is the empty type — no value can ever inhabit it. A field
typed `never` cannot carry a runtime value, so there is nothing to
encode/decode/validate.

Fix — use `unknown` if you really want to accept any value:
  interface User {
-   tag: never;
+   tag: unknown;  // narrow before use
  }

Fix — pick a concrete type matching your real data:
  interface User {
-   tag: never;
+   tag: 'pending' | 'active' | 'done';
  }
FB002Error
Type `{0}` can never be deserialised from binary — the generated function will always fail.
Full build message
Built-in classes like `Map`, `Set`, `WeakMap`, `WeakSet`, `Int8Array`,
`Uint8Array`, `Buffer`, and `Promise` carry runtime state that doesn't
survive a JSON or binary round-trip. Their instance identity is lost the
moment they're serialised.

Fix — convert to a plain object/array before serialising:
  // for Map<K, V>:
  const data = Object.fromEntries(yourMap);
  // for Set<T>:
  const data = [...yourSet];
  // for typed arrays:
  const data = Array.from(yourBuffer);

Fix — change the field type to a serialisable shape:
  interface User {
-   tags: Set<string>;
+   tags: string[];
  }
FB003Error
Type `{0}` can never be deserialised from binary — the generated function will always fail.
Full build message
Functions have no value form to serialise — their closure, prototype,
and bound state aren't representable in JSON or binary.

Fix — drop the function from your type, or replace it with the data the
function would produce:
  interface User {
-   getName: () => string;
+   name: string;
  }
FB004Error
Type `{0}` can never be deserialised from binary — the generated function will always fail.
Full build message
Arrays of un-serialisable elements (`symbol[]`, `(() => void)[]`,
`Map<K, V>[]`, etc.) can't be encoded — every element would need to be
representable, and these aren't. Dropping individual elements would
change the array length, so the encoder refuses rather than silently
shipping a different shape.

Fix — change the element type to something serialisable:
  -  type Items = (() => void)[];
+  type Items = string[];
FB005Error
Type `{0}` can never be deserialised from binary — the generated function will always fail.
Full build message
Built-in classes like `Map`, `Set`, `WeakMap`, `WeakSet`, `Int8Array`,
`Uint8Array`, `Buffer`, and `Promise` carry runtime state that doesn't
survive a JSON or binary round-trip. Their instance identity is lost the
moment they're serialised.

Fix — convert to a plain object/array before serialising:
  // for Map<K, V>:
  const data = Object.fromEntries(yourMap);
  // for Set<T>:
  const data = [...yourSet];
  // for typed arrays:
  const data = Array.from(yourBuffer);

Fix — change the field type to a serialisable shape:
  interface User {
-   tags: Set<string>;
+   tags: string[];
  }
FB006Error
Type `{0}` can never be deserialised from binary — the generated function will always fail.
Full build message
Every `symbol` value carries a unique runtime identity (`Symbol() !==
Symbol()` even with the same description). That identity disappears the
moment it's serialised, and two symbols can't be compared across realms,
workers, or process boundaries. A validator that asserts "this is a
symbol" gives a false sense of safety — the value can't actually
round-trip.

Fix — use a stable string key (often a literal union):
  -  type Status = symbol;
+  type Status = 'pending' | 'active' | 'done';
FB010Warning
Property `{0}` is a function — `fromBinary` does not handle function values, so this property is silently not deserialised.
Full build message
`fromBinary` works on JSON-shaped data; functions don't survive JSON, so
the emitter drops them. The rest of the object's behaviour is unaffected.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If you need a stricter checker that fails on
missing/extra function-typed members, watch the project roadmap.
FB011Warning
Method `{0}` is silently not deserialised by `fromBinary` — methods aren't data.
Full build message
Class and object methods aren't part of the serialisable shape, so
`fromBinary` excludes them. The rest of the type still works.

If you wanted the method's return value validated/serialised, expose it
as a data property instead.
FB012Warning
Static member `{0}` is silently not deserialised by `fromBinary` — statics aren't part of instance data.
Full build message
Class static members live on the class, not on individual instances.
`fromBinary` operates on instance shape, so statics are excluded.
FB013Warning
Symbol-keyed property `{0}` is silently not deserialised by `fromBinary` — symbol keys aren't JSON-representable.
Full build message
JSON only supports string keys; symbol-keyed properties are dropped
from the serialised form. `fromBinary` follows the same rule.

Fix — use a string key:
  -  [Symbol.for('id')]: string;
+  id: string;
FB014Warning
Union member(s) of type `{0}` can't be represented as data — `fromBinary` drops them, so the union is deserialised as its remaining members.
Full build message
A union projects to its serialisable members only: `DataOnly<Date | symbol>`
is `Date`. The dropped member(s) ({0}) carry no JSON-shaped value (symbol,
function, Promise, or a non-serialisable built-in like `Map` / `Set` /
typed arrays), so `fromBinary` deserialised only the members that remain.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If EVERY member of the union is non-serialisable the
projection is `never`, and `fromBinary` throws at build time instead.
FB015Warning
Property `{0}` has a non-serialisable value type (symbol, Promise, or a non-serialisable built-in) — `fromBinary` drops it, so this property is silently not deserialised.
Full build message
`fromBinary` works on JSON-shaped data. A property whose value is a symbol,
a Promise, or a non-serialisable built-in (typed array, ArrayBuffer, …) carries
no JSON-shaped value, so it is dropped: `DataOnly<{ {0}: symbol }>` is `{}`.
The rest of the object's behaviour is unaffected.

Note the difference from a property that is only STRUCTURALLY unserialisable —
`{0}: symbol[]` or `{0}: Map<string, symbol>` — which CANNOT be safely
dropped (DataOnly keeps it as `never[]`): there `fromBinary` throws at build
time instead.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md.
JCP001Error
Internal error: JSON composite `{0}` references primitive entry `{1}` (type `{2}`) which was never rendered — please file an issue.
NE001Error
Property `{0}` is tagged @nonEnumerable but is required — the guard only applies to optional properties, so the tag has no effect. Make it optional (`{0}?`) or remove the tag.
Full build message
The runtime enumerability guard (which lets a value omit a property from
the wire when it isn't an enumerable own property) is applied ONLY to
optional properties. That keeps the decoder's `DataOnly<T>` return type
honest: a guarded property is always one the type already allows to be
absent. A `@nonEnumerable` tag on a REQUIRED property is therefore ignored
— the property still serializes unconditionally.

Fix — make the property optional:
-  /** @nonEnumerable */ token: string;
+  /** @nonEnumerable */ token?: string;
PJ001Error
Type `{0}` can never be encoded to JSON — the generated function will always fail.
Full build message
`never` is the empty type — no value can ever inhabit it. A field
typed `never` cannot carry a runtime value, so there is nothing to
encode/decode/validate.

Fix — use `unknown` if you really want to accept any value:
  interface User {
-   tag: never;
+   tag: unknown;  // narrow before use
  }

Fix — pick a concrete type matching your real data:
  interface User {
-   tag: never;
+   tag: 'pending' | 'active' | 'done';
  }
PJ002Error
Type `{0}` can never be encoded to JSON — the generated function will always fail.
Full build message
Built-in classes like `Map`, `Set`, `WeakMap`, `WeakSet`, `Int8Array`,
`Uint8Array`, `Buffer`, and `Promise` carry runtime state that doesn't
survive a JSON or binary round-trip. Their instance identity is lost the
moment they're serialised.

Fix — convert to a plain object/array before serialising:
  // for Map<K, V>:
  const data = Object.fromEntries(yourMap);
  // for Set<T>:
  const data = [...yourSet];
  // for typed arrays:
  const data = Array.from(yourBuffer);

Fix — change the field type to a serialisable shape:
  interface User {
-   tags: Set<string>;
+   tags: string[];
  }
PJ003Error
Type `{0}` can never be encoded to JSON — the generated function will always fail.
Full build message
Functions have no value form to serialise — their closure, prototype,
and bound state aren't representable in JSON or binary.

Fix — drop the function from your type, or replace it with the data the
function would produce:
  interface User {
-   getName: () => string;
+   name: string;
  }
PJ004Error
Type `{0}` can never be encoded to JSON — the generated function will always fail.
Full build message
Arrays of un-serialisable elements (`symbol[]`, `(() => void)[]`,
`Map<K, V>[]`, etc.) can't be encoded — every element would need to be
representable, and these aren't. Dropping individual elements would
change the array length, so the encoder refuses rather than silently
shipping a different shape.

Fix — change the element type to something serialisable:
  -  type Items = (() => void)[];
+  type Items = string[];
PJ005Error
Type `{0}` can never be encoded to JSON — the generated function will always fail.
Full build message
Every `symbol` value carries a unique runtime identity (`Symbol() !==
Symbol()` even with the same description). That identity disappears the
moment it's serialised, and two symbols can't be compared across realms,
workers, or process boundaries. A validator that asserts "this is a
symbol" gives a false sense of safety — the value can't actually
round-trip.

Fix — use a stable string key (often a literal union):
  -  type Status = symbol;
+  type Status = 'pending' | 'active' | 'done';
PJ010Warning
Property `{0}` is a function — `prepareForJson` does not handle function values, so this property is silently not encoded.
Full build message
`prepareForJson` works on JSON-shaped data; functions don't survive JSON, so
the emitter drops them. The rest of the object's behaviour is unaffected.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If you need a stricter checker that fails on
missing/extra function-typed members, watch the project roadmap.
PJ011Warning
Method `{0}` is silently not encoded by `prepareForJson` — methods aren't data.
Full build message
Class and object methods aren't part of the serialisable shape, so
`prepareForJson` excludes them. The rest of the type still works.

If you wanted the method's return value validated/serialised, expose it
as a data property instead.
PJ012Warning
Static member `{0}` is silently not encoded by `prepareForJson` — statics aren't part of instance data.
Full build message
Class static members live on the class, not on individual instances.
`prepareForJson` operates on instance shape, so statics are excluded.
PJ013Warning
Symbol-keyed property `{0}` is silently not encoded by `prepareForJson` — symbol keys aren't JSON-representable.
Full build message
JSON only supports string keys; symbol-keyed properties are dropped
from the serialised form. `prepareForJson` follows the same rule.

Fix — use a string key:
  -  [Symbol.for('id')]: string;
+  id: string;
PJ014Warning
Union member(s) of type `{0}` can't be represented as data — `prepareForJson` drops them, so the union is encoded as its remaining members.
Full build message
A union projects to its serialisable members only: `DataOnly<Date | symbol>`
is `Date`. The dropped member(s) ({0}) carry no JSON-shaped value (symbol,
function, Promise, or a non-serialisable built-in like `Map` / `Set` /
typed arrays), so `prepareForJson` encoded only the members that remain.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If EVERY member of the union is non-serialisable the
projection is `never`, and `prepareForJson` throws at build time instead.
PJ015Warning
Property `{0}` has a non-serialisable value type (symbol, Promise, or a non-serialisable built-in) — `prepareForJson` drops it, so this property is silently not encoded.
Full build message
`prepareForJson` works on JSON-shaped data. A property whose value is a symbol,
a Promise, or a non-serialisable built-in (typed array, ArrayBuffer, …) carries
no JSON-shaped value, so it is dropped: `DataOnly<{ {0}: symbol }>` is `{}`.
The rest of the object's behaviour is unaffected.

Note the difference from a property that is only STRUCTURALLY unserialisable —
`{0}: symbol[]` or `{0}: Map<string, symbol>` — which CANNOT be safely
dropped (DataOnly keeps it as `never[]`): there `prepareForJson` throws at build
time instead.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md.
PJS001Error
Type `{0}` can never be encoded to JSON — the generated function will always fail.
Full build message
`never` is the empty type — no value can ever inhabit it. A field
typed `never` cannot carry a runtime value, so there is nothing to
encode/decode/validate.

Fix — use `unknown` if you really want to accept any value:
  interface User {
-   tag: never;
+   tag: unknown;  // narrow before use
  }

Fix — pick a concrete type matching your real data:
  interface User {
-   tag: never;
+   tag: 'pending' | 'active' | 'done';
  }
PJS002Error
Type `{0}` can never be encoded to JSON — the generated function will always fail.
Full build message
Built-in classes like `Map`, `Set`, `WeakMap`, `WeakSet`, `Int8Array`,
`Uint8Array`, `Buffer`, and `Promise` carry runtime state that doesn't
survive a JSON or binary round-trip. Their instance identity is lost the
moment they're serialised.

Fix — convert to a plain object/array before serialising:
  // for Map<K, V>:
  const data = Object.fromEntries(yourMap);
  // for Set<T>:
  const data = [...yourSet];
  // for typed arrays:
  const data = Array.from(yourBuffer);

Fix — change the field type to a serialisable shape:
  interface User {
-   tags: Set<string>;
+   tags: string[];
  }
PJS003Error
Type `{0}` can never be encoded to JSON — the generated function will always fail.
Full build message
Functions have no value form to serialise — their closure, prototype,
and bound state aren't representable in JSON or binary.

Fix — drop the function from your type, or replace it with the data the
function would produce:
  interface User {
-   getName: () => string;
+   name: string;
  }
PJS004Error
Type `{0}` can never be encoded to JSON — the generated function will always fail.
Full build message
Arrays of un-serialisable elements (`symbol[]`, `(() => void)[]`,
`Map<K, V>[]`, etc.) can't be encoded — every element would need to be
representable, and these aren't. Dropping individual elements would
change the array length, so the encoder refuses rather than silently
shipping a different shape.

Fix — change the element type to something serialisable:
  -  type Items = (() => void)[];
+  type Items = string[];
PJS005Error
Type `{0}` can never be encoded to JSON — the generated function will always fail.
Full build message
Every `symbol` value carries a unique runtime identity (`Symbol() !==
Symbol()` even with the same description). That identity disappears the
moment it's serialised, and two symbols can't be compared across realms,
workers, or process boundaries. A validator that asserts "this is a
symbol" gives a false sense of safety — the value can't actually
round-trip.

Fix — use a stable string key (often a literal union):
  -  type Status = symbol;
+  type Status = 'pending' | 'active' | 'done';
PJS010Warning
Property `{0}` is a function — `prepareForJsonSafe` does not handle function values, so this property is silently not encoded.
Full build message
`prepareForJsonSafe` works on JSON-shaped data; functions don't survive JSON, so
the emitter drops them. The rest of the object's behaviour is unaffected.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If you need a stricter checker that fails on
missing/extra function-typed members, watch the project roadmap.
PJS011Warning
Method `{0}` is silently not encoded by `prepareForJsonSafe` — methods aren't data.
Full build message
Class and object methods aren't part of the serialisable shape, so
`prepareForJsonSafe` excludes them. The rest of the type still works.

If you wanted the method's return value validated/serialised, expose it
as a data property instead.
PJS012Warning
Static member `{0}` is silently not encoded by `prepareForJsonSafe` — statics aren't part of instance data.
Full build message
Class static members live on the class, not on individual instances.
`prepareForJsonSafe` operates on instance shape, so statics are excluded.
PJS013Warning
Symbol-keyed property `{0}` is silently not encoded by `prepareForJsonSafe` — symbol keys aren't JSON-representable.
Full build message
JSON only supports string keys; symbol-keyed properties are dropped
from the serialised form. `prepareForJsonSafe` follows the same rule.

Fix — use a string key:
  -  [Symbol.for('id')]: string;
+  id: string;
PJS014Warning
Union member(s) of type `{0}` can't be represented as data — `prepareForJsonSafe` drops them, so the union is encoded as its remaining members.
Full build message
A union projects to its serialisable members only: `DataOnly<Date | symbol>`
is `Date`. The dropped member(s) ({0}) carry no JSON-shaped value (symbol,
function, Promise, or a non-serialisable built-in like `Map` / `Set` /
typed arrays), so `prepareForJsonSafe` encoded only the members that remain.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If EVERY member of the union is non-serialisable the
projection is `never`, and `prepareForJsonSafe` throws at build time instead.
PJS015Warning
Property `{0}` has a non-serialisable value type (symbol, Promise, or a non-serialisable built-in) — `prepareForJsonSafe` drops it, so this property is silently not encoded.
Full build message
`prepareForJsonSafe` works on JSON-shaped data. A property whose value is a symbol,
a Promise, or a non-serialisable built-in (typed array, ArrayBuffer, …) carries
no JSON-shaped value, so it is dropped: `DataOnly<{ {0}: symbol }>` is `{}`.
The rest of the object's behaviour is unaffected.

Note the difference from a property that is only STRUCTURALLY unserialisable —
`{0}: symbol[]` or `{0}: Map<string, symbol>` — which CANNOT be safely
dropped (DataOnly keeps it as `never[]`): there `prepareForJsonSafe` throws at build
time instead.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md.
RJ001Error
Type `{0}` can never be decoded from JSON — the generated function will always fail.
Full build message
`never` is the empty type — no value can ever inhabit it. A field
typed `never` cannot carry a runtime value, so there is nothing to
encode/decode/validate.

Fix — use `unknown` if you really want to accept any value:
  interface User {
-   tag: never;
+   tag: unknown;  // narrow before use
  }

Fix — pick a concrete type matching your real data:
  interface User {
-   tag: never;
+   tag: 'pending' | 'active' | 'done';
  }
RJ002Error
Type `{0}` can never be decoded from JSON — the generated function will always fail.
Full build message
Built-in classes like `Map`, `Set`, `WeakMap`, `WeakSet`, `Int8Array`,
`Uint8Array`, `Buffer`, and `Promise` carry runtime state that doesn't
survive a JSON or binary round-trip. Their instance identity is lost the
moment they're serialised.

Fix — convert to a plain object/array before serialising:
  // for Map<K, V>:
  const data = Object.fromEntries(yourMap);
  // for Set<T>:
  const data = [...yourSet];
  // for typed arrays:
  const data = Array.from(yourBuffer);

Fix — change the field type to a serialisable shape:
  interface User {
-   tags: Set<string>;
+   tags: string[];
  }
RJ003Error
Type `{0}` can never be decoded from JSON — the generated function will always fail.
Full build message
Functions have no value form to serialise — their closure, prototype,
and bound state aren't representable in JSON or binary.

Fix — drop the function from your type, or replace it with the data the
function would produce:
  interface User {
-   getName: () => string;
+   name: string;
  }
RJ004Error
Type `{0}` can never be decoded from JSON — the generated function will always fail.
Full build message
Arrays of un-serialisable elements (`symbol[]`, `(() => void)[]`,
`Map<K, V>[]`, etc.) can't be encoded — every element would need to be
representable, and these aren't. Dropping individual elements would
change the array length, so the encoder refuses rather than silently
shipping a different shape.

Fix — change the element type to something serialisable:
  -  type Items = (() => void)[];
+  type Items = string[];
RJ005Error
Type `{0}` can never be decoded from JSON — the generated function will always fail.
Full build message
Every `symbol` value carries a unique runtime identity (`Symbol() !==
Symbol()` even with the same description). That identity disappears the
moment it's serialised, and two symbols can't be compared across realms,
workers, or process boundaries. A validator that asserts "this is a
symbol" gives a false sense of safety — the value can't actually
round-trip.

Fix — use a stable string key (often a literal union):
  -  type Status = symbol;
+  type Status = 'pending' | 'active' | 'done';
RJ010Warning
Property `{0}` is a function — `restoreFromJson` does not handle function values, so this property is silently not decoded.
Full build message
`restoreFromJson` works on JSON-shaped data; functions don't survive JSON, so
the emitter drops them. The rest of the object's behaviour is unaffected.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If you need a stricter checker that fails on
missing/extra function-typed members, watch the project roadmap.
RJ011Warning
Method `{0}` is silently not decoded by `restoreFromJson` — methods aren't data.
Full build message
Class and object methods aren't part of the serialisable shape, so
`restoreFromJson` excludes them. The rest of the type still works.

If you wanted the method's return value validated/serialised, expose it
as a data property instead.
RJ012Warning
Static member `{0}` is silently not decoded by `restoreFromJson` — statics aren't part of instance data.
Full build message
Class static members live on the class, not on individual instances.
`restoreFromJson` operates on instance shape, so statics are excluded.
RJ013Warning
Symbol-keyed property `{0}` is silently not decoded by `restoreFromJson` — symbol keys aren't JSON-representable.
Full build message
JSON only supports string keys; symbol-keyed properties are dropped
from the serialised form. `restoreFromJson` follows the same rule.

Fix — use a string key:
  -  [Symbol.for('id')]: string;
+  id: string;
RJ014Warning
Union member(s) of type `{0}` can't be represented as data — `restoreFromJson` drops them, so the union is decoded as its remaining members.
Full build message
A union projects to its serialisable members only: `DataOnly<Date | symbol>`
is `Date`. The dropped member(s) ({0}) carry no JSON-shaped value (symbol,
function, Promise, or a non-serialisable built-in like `Map` / `Set` /
typed arrays), so `restoreFromJson` decoded only the members that remain.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If EVERY member of the union is non-serialisable the
projection is `never`, and `restoreFromJson` throws at build time instead.
RJ015Warning
Property `{0}` has a non-serialisable value type (symbol, Promise, or a non-serialisable built-in) — `restoreFromJson` drops it, so this property is silently not decoded.
Full build message
`restoreFromJson` works on JSON-shaped data. A property whose value is a symbol,
a Promise, or a non-serialisable built-in (typed array, ArrayBuffer, …) carries
no JSON-shaped value, so it is dropped: `DataOnly<{ {0}: symbol }>` is `{}`.
The rest of the object's behaviour is unaffected.

Note the difference from a property that is only STRUCTURALLY unserialisable —
`{0}: symbol[]` or `{0}: Map<string, symbol>` — which CANNOT be safely
dropped (DataOnly keeps it as `never[]`): there `restoreFromJson` throws at build
time instead.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md.
SJ001Error
Type `{0}` can never be stringified to JSON — the generated function will always fail.
Full build message
`never` is the empty type — no value can ever inhabit it. A field
typed `never` cannot carry a runtime value, so there is nothing to
encode/decode/validate.

Fix — use `unknown` if you really want to accept any value:
  interface User {
-   tag: never;
+   tag: unknown;  // narrow before use
  }

Fix — pick a concrete type matching your real data:
  interface User {
-   tag: never;
+   tag: 'pending' | 'active' | 'done';
  }
SJ002Error
Type `{0}` can never be stringified to JSON — the generated function will always fail.
Full build message
Built-in classes like `Map`, `Set`, `WeakMap`, `WeakSet`, `Int8Array`,
`Uint8Array`, `Buffer`, and `Promise` carry runtime state that doesn't
survive a JSON or binary round-trip. Their instance identity is lost the
moment they're serialised.

Fix — convert to a plain object/array before serialising:
  // for Map<K, V>:
  const data = Object.fromEntries(yourMap);
  // for Set<T>:
  const data = [...yourSet];
  // for typed arrays:
  const data = Array.from(yourBuffer);

Fix — change the field type to a serialisable shape:
  interface User {
-   tags: Set<string>;
+   tags: string[];
  }
SJ003Error
Type `{0}` can never be stringified to JSON — the generated function will always fail.
Full build message
Functions have no value form to serialise — their closure, prototype,
and bound state aren't representable in JSON or binary.

Fix — drop the function from your type, or replace it with the data the
function would produce:
  interface User {
-   getName: () => string;
+   name: string;
  }
SJ004Error
Type `{0}` can never be stringified to JSON — the generated function will always fail.
Full build message
Arrays of un-serialisable elements (`symbol[]`, `(() => void)[]`,
`Map<K, V>[]`, etc.) can't be encoded — every element would need to be
representable, and these aren't. Dropping individual elements would
change the array length, so the encoder refuses rather than silently
shipping a different shape.

Fix — change the element type to something serialisable:
  -  type Items = (() => void)[];
+  type Items = string[];
SJ005Error
Type `{0}` can never be stringified to JSON — the generated function will always fail.
Full build message
Every `symbol` value carries a unique runtime identity (`Symbol() !==
Symbol()` even with the same description). That identity disappears the
moment it's serialised, and two symbols can't be compared across realms,
workers, or process boundaries. A validator that asserts "this is a
symbol" gives a false sense of safety — the value can't actually
round-trip.

Fix — use a stable string key (often a literal union):
  -  type Status = symbol;
+  type Status = 'pending' | 'active' | 'done';
SJ010Warning
Property `{0}` is a function — `stringifyJson` does not handle function values, so this property is silently not stringified.
Full build message
`stringifyJson` works on JSON-shaped data; functions don't survive JSON, so
the emitter drops them. The rest of the object's behaviour is unaffected.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If you need a stricter checker that fails on
missing/extra function-typed members, watch the project roadmap.
SJ011Warning
Method `{0}` is silently not stringified by `stringifyJson` — methods aren't data.
Full build message
Class and object methods aren't part of the serialisable shape, so
`stringifyJson` excludes them. The rest of the type still works.

If you wanted the method's return value validated/serialised, expose it
as a data property instead.
SJ012Warning
Static member `{0}` is silently not stringified by `stringifyJson` — statics aren't part of instance data.
Full build message
Class static members live on the class, not on individual instances.
`stringifyJson` operates on instance shape, so statics are excluded.
SJ013Warning
Symbol-keyed property `{0}` is silently not stringified by `stringifyJson` — symbol keys aren't JSON-representable.
Full build message
JSON only supports string keys; symbol-keyed properties are dropped
from the serialised form. `stringifyJson` follows the same rule.

Fix — use a string key:
  -  [Symbol.for('id')]: string;
+  id: string;
SJ014Warning
Union member(s) of type `{0}` can't be represented as data — `stringifyJson` drops them, so the union is stringified as its remaining members.
Full build message
A union projects to its serialisable members only: `DataOnly<Date | symbol>`
is `Date`. The dropped member(s) ({0}) carry no JSON-shaped value (symbol,
function, Promise, or a non-serialisable built-in like `Map` / `Set` /
typed arrays), so `stringifyJson` stringified only the members that remain.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If EVERY member of the union is non-serialisable the
projection is `never`, and `stringifyJson` throws at build time instead.
SJ015Warning
Property `{0}` has a non-serialisable value type (symbol, Promise, or a non-serialisable built-in) — `stringifyJson` drops it, so this property is silently not stringified.
Full build message
`stringifyJson` works on JSON-shaped data. A property whose value is a symbol,
a Promise, or a non-serialisable built-in (typed array, ArrayBuffer, …) carries
no JSON-shaped value, so it is dropped: `DataOnly<{ {0}: symbol }>` is `{}`.
The rest of the object's behaviour is unaffected.

Note the difference from a property that is only STRUCTURALLY unserialisable —
`{0}: symbol[]` or `{0}: Map<string, symbol>` — which CANNOT be safely
dropped (DataOnly keeps it as `never[]`): there `stringifyJson` throws at build
time instead.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md.
TB001Error
Type `{0}` can never be serialised to binary — the generated function will always fail.
Full build message
`never` is the empty type — no value can ever inhabit it. A field
typed `never` cannot carry a runtime value, so there is nothing to
encode/decode/validate.

Fix — use `unknown` if you really want to accept any value:
  interface User {
-   tag: never;
+   tag: unknown;  // narrow before use
  }

Fix — pick a concrete type matching your real data:
  interface User {
-   tag: never;
+   tag: 'pending' | 'active' | 'done';
  }
TB002Error
Type `{0}` can never be serialised to binary — the generated function will always fail.
Full build message
Built-in classes like `Map`, `Set`, `WeakMap`, `WeakSet`, `Int8Array`,
`Uint8Array`, `Buffer`, and `Promise` carry runtime state that doesn't
survive a JSON or binary round-trip. Their instance identity is lost the
moment they're serialised.

Fix — convert to a plain object/array before serialising:
  // for Map<K, V>:
  const data = Object.fromEntries(yourMap);
  // for Set<T>:
  const data = [...yourSet];
  // for typed arrays:
  const data = Array.from(yourBuffer);

Fix — change the field type to a serialisable shape:
  interface User {
-   tags: Set<string>;
+   tags: string[];
  }
TB003Error
Type `{0}` can never be serialised to binary — the generated function will always fail.
Full build message
Functions have no value form to serialise — their closure, prototype,
and bound state aren't representable in JSON or binary.

Fix — drop the function from your type, or replace it with the data the
function would produce:
  interface User {
-   getName: () => string;
+   name: string;
  }
TB004Error
Type `{0}` can never be serialised to binary — the generated function will always fail.
Full build message
Arrays of un-serialisable elements (`symbol[]`, `(() => void)[]`,
`Map<K, V>[]`, etc.) can't be encoded — every element would need to be
representable, and these aren't. Dropping individual elements would
change the array length, so the encoder refuses rather than silently
shipping a different shape.

Fix — change the element type to something serialisable:
  -  type Items = (() => void)[];
+  type Items = string[];
TB005Error
Type `{0}` can never be serialised to binary — the generated function will always fail.
Full build message
Built-in classes like `Map`, `Set`, `WeakMap`, `WeakSet`, `Int8Array`,
`Uint8Array`, `Buffer`, and `Promise` carry runtime state that doesn't
survive a JSON or binary round-trip. Their instance identity is lost the
moment they're serialised.

Fix — convert to a plain object/array before serialising:
  // for Map<K, V>:
  const data = Object.fromEntries(yourMap);
  // for Set<T>:
  const data = [...yourSet];
  // for typed arrays:
  const data = Array.from(yourBuffer);

Fix — change the field type to a serialisable shape:
  interface User {
-   tags: Set<string>;
+   tags: string[];
  }
TB006Error
Type `{0}` can never be serialised to binary — the generated function will always fail.
Full build message
Every `symbol` value carries a unique runtime identity (`Symbol() !==
Symbol()` even with the same description). That identity disappears the
moment it's serialised, and two symbols can't be compared across realms,
workers, or process boundaries. A validator that asserts "this is a
symbol" gives a false sense of safety — the value can't actually
round-trip.

Fix — use a stable string key (often a literal union):
  -  type Status = symbol;
+  type Status = 'pending' | 'active' | 'done';
TB010Warning
Property `{0}` is a function — `toBinary` does not handle function values, so this property is silently not serialised.
Full build message
`toBinary` works on JSON-shaped data; functions don't survive JSON, so
the emitter drops them. The rest of the object's behaviour is unaffected.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If you need a stricter checker that fails on
missing/extra function-typed members, watch the project roadmap.
TB011Warning
Method `{0}` is silently not serialised by `toBinary` — methods aren't data.
Full build message
Class and object methods aren't part of the serialisable shape, so
`toBinary` excludes them. The rest of the type still works.

If you wanted the method's return value validated/serialised, expose it
as a data property instead.
TB012Warning
Static member `{0}` is silently not serialised by `toBinary` — statics aren't part of instance data.
Full build message
Class static members live on the class, not on individual instances.
`toBinary` operates on instance shape, so statics are excluded.
TB013Warning
Symbol-keyed property `{0}` is silently not serialised by `toBinary` — symbol keys aren't JSON-representable.
Full build message
JSON only supports string keys; symbol-keyed properties are dropped
from the serialised form. `toBinary` follows the same rule.

Fix — use a string key:
  -  [Symbol.for('id')]: string;
+  id: string;
TB014Warning
Union member(s) of type `{0}` can't be represented as data — `toBinary` drops them, so the union is serialised as its remaining members.
Full build message
A union projects to its serialisable members only: `DataOnly<Date | symbol>`
is `Date`. The dropped member(s) ({0}) carry no JSON-shaped value (symbol,
function, Promise, or a non-serialisable built-in like `Map` / `Set` /
typed arrays), so `toBinary` serialised only the members that remain.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If EVERY member of the union is non-serialisable the
projection is `never`, and `toBinary` throws at build time instead.
TB015Warning
Property `{0}` has a non-serialisable value type (symbol, Promise, or a non-serialisable built-in) — `toBinary` drops it, so this property is silently not serialised.
Full build message
`toBinary` works on JSON-shaped data. A property whose value is a symbol,
a Promise, or a non-serialisable built-in (typed array, ArrayBuffer, …) carries
no JSON-shaped value, so it is dropped: `DataOnly<{ {0}: symbol }>` is `{}`.
The rest of the object's behaviour is unaffected.

Note the difference from a property that is only STRUCTURALLY unserialisable —
`{0}: symbol[]` or `{0}: Map<string, symbol>` — which CANNOT be safely
dropped (DataOnly keeps it as `never[]`): there `toBinary` throws at build
time instead.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md.

Unknown keys

From hasUnknownKeys, cloneExactShape, and the rest of that family.

CES001Error
`cloneExactShape` does not support unions with object members — the emitter cannot know which declared shape to rebuild at runtime.
Full build message
A clone built from the declared shape needs to know WHICH union arm the
runtime value matches; v1 has no arm discrimination, and silently keeping
unknown keys would defeat the strip guarantee, so the build fails instead.

Workarounds: narrow the value to one arm before cloning (one
`createCloneExactShapeFn<Arm>()` per arm), or restructure the union into a
single object with optional properties.
CES003Error
`cloneExactShape` cannot clone a function-typed value.
Full build message
Functions aren't data — there is no declared shape to rebuild. Function-typed
PROPERTIES are dropped from the clone (CES010/CES011); a function at the root
or a propagating position fails the build.
CES010Warning
Property `{0}` is a function — `cloneExactShape` cannot rebuild it, so it is kept on the clone, SHARED BY REFERENCE.
Full build message
Declared members are never dropped (only UNDECLARED keys are — that is the
strip guarantee). Functions cannot be rebuilt from a declared shape, so the
clone's property points at the SAME function as the input's. Class METHODS
differ: they ride the shared prototype and are not copied as own props
(CES011).
CES011Warning
Method `{0}` is not copied onto the clone's own properties — methods ride the prototype.
Full build message
For a plain class instance the clone preserves the PROTOTYPE
(`Object.create(Object.getPrototypeOf(v))`), so methods keep working via the
prototype chain; they are simply not copied as own properties. For object
literals a method-typed member is omitted like any function value.
CES012Warning
Static member `{0}` is not part of instance data — `cloneExactShape` skips it.
Full build message
Statics live on the class, not the instance; the clone rebuilds instance
data only.
CES015Warning
Property `{0}` has a value type `cloneExactShape` cannot rebuild (symbol, Promise, or a non-serialisable built-in) — it is kept on the clone, SHARED BY REFERENCE.
Full build message
Declared members are never dropped (only UNDECLARED keys are — that is the
strip guarantee). A value the emitter cannot rebuild passes through by
reference instead: the clone's property points at the SAME handle as the
input's, so mutations through it are visible on both sides. Register
`overrideCloneExactShape<T>()` if this type needs custom copying.
HUK010Warning
Property `{0}` is a function — `hasUnknownKeys` does not handle function values, so this property is silently not checked.
Full build message
`hasUnknownKeys` works on JSON-shaped data; functions don't survive JSON, so
the emitter drops them. The rest of the object's behaviour is unaffected.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If you need a stricter checker that fails on
missing/extra function-typed members, watch the project roadmap.
UKE010Warning
Property `{0}` is a function — `unknownKeyErrors` does not handle function values, so this property is silently not checked.
Full build message
`unknownKeyErrors` works on JSON-shaped data; functions don't survive JSON, so
the emitter drops them. The rest of the object's behaviour is unaffected.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If you need a stricter checker that fails on
missing/extra function-typed members, watch the project roadmap.
UKU010Warning
Property `{0}` is a function — `unknownKeysToUndefined` does not handle function values, so this property is silently not cleared.
Full build message
`unknownKeysToUndefined` works on JSON-shaped data; functions don't survive JSON, so
the emitter drops them. The rest of the object's behaviour is unaffected.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If you need a stricter checker that fails on
missing/extra function-typed members, watch the project roadmap.
UKW010Warning
Property `{0}` is a function — `unknownKeysToUndefinedWire` does not handle function values, so this property is silently not cleared.
Full build message
`unknownKeysToUndefinedWire` works on JSON-shaped data; functions don't survive JSON, so
the emitter drops them. The rest of the object's behaviour is unaffected.

This is by design — see the "validate contract — serializable data only"
section in CLAUDE.md. If you need a stricter checker that fails on
missing/extra function-typed members, watch the project roadmap.

Type formats

From the pattern and sample checks on a TypeFormat.

FMT001Error
TypeFormat mockSample "{0}" does not match its pattern /{1}/ — fix the sample or the pattern.
FMT002Error
Invalid type-format params — {0}
FMT003Error
TypeFormat mockSample violates a sibling constraint — {0}
Full build message
A mockSample is meant to be a canonical VALID value for the format, so it
must satisfy the format's own statically checkable siblings (length /
minLength / maxLength, and the plain-string allowedChars / disallowedChars /
disallowedValues ops). A sample that its siblings reject means
`createMockDataFn` would either produce an invalid value or filter every
sample out and throw at mock time.

Lengths are counted in UTF-16 code units, exactly as the emitted validator's
`.length` check counts them.

Fix — adjust the offending sample(s), or relax the constraint:
  -  String<{minLength: 5; pattern: {source: '^b+$'; mockSamples: ['b', 'bb']}}>
+  String<{minLength: 1; pattern: {source: '^b+$'; mockSamples: ['b', 'bb']}}>
FMT004Error
TypeFormat pattern /{0}/ carries mockSamples but uses JS-only regex features RE2 can't compile ({1}) — samples can't be verified at build time.
Full build message
The build-time sample check compiles the pattern with Go's RE2 engine, which
doesn't support JS-only features (lookarounds, backreferences). The build
fails closed rather than ship samples it can't verify.

Fix — set `allowUncheckedPatterns` (plugin option / tsconfig plugin key)
to assert that the JS linter owns the check, then wire the ts-runtypes lint
plugin into your editor + CI: it evaluates the real `RegExp.test(sample)` and
reports any mismatch (as FMT001) at the definition site.

Fix — or rewrite the pattern using RE2-compatible syntax so the fast
build-time check can run (no lookarounds / backreferences).

Pure functions

From the purity rules for registerPureFnFactory.

PFE9004Error
Duplicate `registerPureFnFactory` for `{0}` with a different body — only one definition can win.
Full build message
Two calls register the same `namespace::functionId` key but the factory
bodies differ. The cache can only hold one definition, so one call site
silently loses its version at runtime.

Fix — make all registrations identical, or pick one canonical site and
delete the others. The Related: line above points at the first
registration the extractor saw.
PFE9005Error
Pure-fn factory `{0}` uses destructured parameters — only simple identifier params are supported.
Full build message
The build inlines parameter references by name when it materialises the
factory. Destructuring patterns (`({a, b})`, `([x, y])`) don't have a
single name to substitute.

Fix — destructure inside the body:
  -  registerPureFnFactory('ns::fn', (utl) => ({a, b}) => ...);
+  registerPureFnFactory('ns::fn', (utl) => (params) => {
+    const {a, b} = params;
+    return ...;
+  });
PFE9006Error
`this` is not allowed inside a `registerPureFnFactory` factory body — pure functions can't depend on a calling context.
Full build message
Pure functions are materialised standalone at build time; there's no
`this` to bind to.

Fix — replace `this` with an explicit parameter, or move the function
out of the class/object method that owns the `this`:
  registerPureFnFactory('ns::fn', (utl) => (self, input) => {
    return self.field + input;
  });
PFE9007Error
`async`/`await` is not allowed inside a `registerPureFnFactory` factory body.
Full build message
Pure functions must run synchronously so the build can call them at
compile time. `async` introduces a Promise that won't resolve until
runtime.

Fix — make the factory synchronous; move async work to the caller:
  registerPureFnFactory('ns::fn', (utl) => {
-   return async (input) => { const r = await heavy(); return r; };
+   return (resolvedValue) => transform(resolvedValue);
  });
PFE9008Error
`yield` / generators are not allowed inside a `registerPureFnFactory` factory body.
Full build message
Generators carry resumption state that can't be materialised
statically.

Fix — return an array or a plain iterable instead:
  registerPureFnFactory('ns::fn', (utl) => (input) => {
    return [...computeAll(input)];
  });
PFE9009Error
`import()` is not allowed inside a `registerPureFnFactory` factory body.
Full build message
Dynamic imports load modules at runtime — the build needs every
dependency available statically.

Fix — use a top-level `import` statement, or pass the imported module
in as a parameter.
PFE9010Error
`{0}` is not allowed inside a `registerPureFnFactory` factory body.
Full build message
Globals like `eval`, `Function`, `fetch`, `XMLHttpRequest`, `require`,
`process`, `globalThis`, `window`, `document` are blocked from pure-fn
bodies — they either execute arbitrary code or depend on a runtime
environment the build can't reproduce.

Fix — remove the reference, or pass the needed value in as a parameter.
PFE9011Error
`{0}` is captured from outer scope inside a `registerPureFnFactory` factory — pure functions can't reach outside their own body.
Full build message
The build inlines factory bodies without their lexical environment, so
any free variable becomes `undefined` at runtime.

Fix — pass `{0}` in as a parameter:
  registerPureFnFactory('ns::fn', (utl) => ({0}, value) => ...);

Fix — inline its value if it's a known constant:
  registerPureFnFactory('ns::fn', (utl) => (value) => {
    const {0} = 42;
    ...
  });

Fix — import `{0}` directly inside the factory if it's a module export.
PFE9012Error
Pure-fn `{0}` is referenced by a RT function but never registered — call `registerPureFnFactory('{1}::{2}', …)` first.

A generated validator or encoder calls a helper (a pure function) that was never registered, so the built output would fail the moment it runs. This almost always means a source file that registers the helper with registerPureFnFactory is not part of the compile. Import the ts-runtypes entry that provides it, or include the file that registers it, so the build can see the definition.

import {registerPureFnFactory} from '@ts-runtypes/core';
registerPureFnFactory('rt::newRunTypeErr', (utl) => (message) => new Error(message));
Full build message
A RT validator/encoder calls `utl.usePureFn('{0}')` (or similar) but
no `registerPureFnFactory` call with that namespace+function pair was
found in any scanned source file.

Fix — register the function in the expected location ({3}, if known).
Make sure the file is included in the scan set.
PFE9013Error
`{0}.{1}` dependency argument must be a string literal or a same-scope `const` string.
Full build message
`utl.usePureFn` / `utl.getPureFn` need a static key so the build can
verify the referenced pure-fn is registered.

Fix:
  -  const key = buildKey();
-  return utl.usePureFn(key)(input);
+  return utl.usePureFn('rt::myFn')(input);

Overrides

From custom per-type function overrides.

OVR001Error
Duplicate override for `{0}` — there can be exactly one override per (type, function).
Full build message
Two `overrideX<T>()` declarations target the same type and the same
function family. Which one wins would depend on scan order, so a second
override is rejected regardless of its body. The Related: line above
points at the override that was registered first.

Fix — keep one canonical override and delete the other:
-  overrideValidate<User>((utl) => (value) => checkA(value));  // first
-  overrideValidate<User>((utl) => (value) => checkB(value));  // duplicate
+  overrideValidate<User>((utl) => (value) => checkA(value) && checkB(value));
OVR002Error
Override entry `{0}` references compiled function `{1}` which did not render — this would throw at runtime, so the build stops.
Full build message
An override redirect body loads its compiled function from the cache
(`usePureFn('cfn::…')`), but that module never rendered into the entry
graph. Calling the override would throw at runtime, so the build surfaces
the miss now. This is an internal emitter tripwire and should never fire
in normal operation.

Fix — re-run with a clean cache first (delete the .runtypes cache dir /
restart the dev server). If it persists, the emitter dropped a module it
should have rendered: please open an issue with the type + override that
triggers it.
OVR010Warning
Overriding `validate` for this type also changes how JSON and binary decoders narrow unions containing it.
Full build message
`validate` is a shared dependency across function families: JSON and
binary union decoders call the member validators to pick the matching
branch. An `overrideValidate<T>()` therefore reaches past
`createValidateFn<T>()` — decoders of any union containing T now narrow
with YOUR function.

This is informational; the build proceeds. If the override should only
affect direct validation, give the union members a discriminant so
decoders never fall back to member validation:
  type Event = {kind: 'click'; x: number} | {kind: 'key'; code: string};

Enrichment files

From ts-runtypes check and the lint rules over generated FriendlyText and MockData files.

FT002Error
Unknown field `{0}` — the type does not declare it, so this FriendlyText entry is dead.
Full build message
The FriendlyText map names a field the source type does not have
(removed, renamed, or a typo). Its labels and messages can never be
used.

Example — `nick` no longer exists on the type:
  interface User { name: string }
  export const friendlyUser: FriendlyText<User> = {
    name: {rt$label: 'Name'},
-   nick: {rt$label: 'Nickname'},
  };

Fix — remove the entry, or re-run the reconcile so the mirror follows
the type (a renamed field carries its authored values along):
  ts-runtypes enrich <source.ts> <Type> --update
FT003Warning
Error key `{0}` is not a declared constraint of this field — the message can never fire.
Full build message
`rt$errors` keys must name a failure the field can actually produce:
`type`, `rt$default`, or one of the field's declared format constraints
(`minLength`, `pattern`, `min`, …). An undeclared key is dead
configuration.

Example — the field has no `maxLength` constraint:
  interface User { name: string & FormatString<{minLength: 2}> }
  export const friendlyUser: FriendlyText<User> = {
    name: {
      rt$errors: {
        minLength: 'Name needs at least 2 characters',
-       maxLength: 'Name is too long',
      },
    },
  };

Fix — remove the key, or declare the matching constraint on the field's
TypeFormat so the message has a failure to describe.
FT005Warning
Unknown placeholder `$[{0}]` — expected one of `$[label]`, `$[val]`, `$[path]`, `$[index]`.
Full build message
Error-message templates substitute a fixed placeholder set; an unknown
name renders literally instead of substituting.

Example:
- rt$errors: {minLength: '$[name] is too short'}
+ rt$errors: {minLength: '$[label] is too short'}

Fix — use one of the recognised placeholders, or write the literal text
without the `$[…]` wrapper.
FT006Error
Plural error template is missing the mandatory `other` arm — the render has no backstop.
Full build message
Plural templates render the CLDR arm matching the count, and `other` is
the arm every locale falls back to. Without it some counts have no
message at all.

Example:
  rt$errors: {
    minLength: {
      one: 'Needs one more character',
+     other: 'Needs $[val] more characters',
    },
  }

Fix — add the `other` arm to the plural object.
FT007Warning
Unknown plural arm `{0}` — CLDR categories are `zero`, `one`, `two`, `few`, `many`, `other`.
Full build message
Plural template keys must be CLDR plural categories; anything else can
never be selected by any locale's plural rules.

Example:
  rt$errors: {
    minLength: {
-     single: 'Needs one more character',
+     one: 'Needs one more character',
      other: 'Needs $[val] more characters',
    },
  }

Fix — rename the arm to one of the six categories, or remove it.
FT008Warning
Constraint `{0}` carries no count — a plural template here has dead arms; use a plain string.
Full build message
Only count-bearing constraints (`minLength`, `maxLength`, `min`, `max`,
…) can select a plural arm. On a non-count constraint only `other` ever
renders, so the remaining arms are dead configuration.

Example — `pattern` has no count:
  rt$errors: {
-   pattern: {one: 'One bad character', other: 'Invalid characters'},
+   pattern: 'Only letters and numbers are allowed',
  }

Fix — replace the plural object with a plain string message.
FT009Error
`rt$default` is mutually exclusive with per-constraint messages — use one mode or the other.
Full build message
An `rt$errors` record is either ONE `rt$default` catch-all or a set of
per-constraint keys, mirroring the TypeScript union. Mixing them makes
the intent ambiguous (which message wins?).

Example:
  rt$errors: {
-   rt$default: 'Invalid name',
    minLength: 'Name is too short',
  }

Fix — keep `{rt$default: '…'}` alone, or keep the per-constraint keys
and drop `rt$default`.
FT011Error
Property `{0}` collides with the reserved `rt$` enrichment prefix — the type cannot be enriched.
Full build message
`rt$`-prefixed keys are reserved for enrichment meta (`rt$label`,
`rt$errors`, `rt$items`, …); a source property with that prefix is
indistinguishable from node meta, so gen refuses the type and the
FriendlyType checker reports it here.

Fix — rename the property (a plain `$` prefix is fine; only `rt$` is
reserved):
  interface Config {
-   rt$mode: string;
+   $mode: string;
  }
FT020Error
Unfilled `@todo` placeholder — fill in the real labels/messages, then delete the `@todo` line.
Full build message
The generator stamps a `@todo` line on every freshly-scaffolded const in
a FriendlyText mirror file. It means "this skeleton still carries
generated blanks". A clean, committed mirror has none.

Example — a fresh scaffold:
  /** @rtType User#a1b2c3 @rtIds {name: d4e5f6} */
- // @todo: generated skeleton — fill in real data, then delete this line
  export const friendlyUser: FriendlyText<User> = {
-   name: {rt$label: ''},
+   name: {rt$label: 'Name'},
  };

Fix — author the real labels and error messages for the const, then
delete the whole `@todo` line (the compiler never removes it for you).
FT021Error
Stale `@rtOrphan` carcass — run `ts-runtypes enrich --prune` to remove it (or restore the type).
Full build message
The reconcile commented this FriendlyText const out because its source
type was deleted or renamed. The carcass preserves your authored labels
and messages so a reappearing type can restore them — but a clean,
committed mirror has none.

Fix — if the type is really gone, prune the carcass:
  ts-runtypes enrich --prune

Fix — if the type was renamed, re-run the reconcile; a matching carcass
is restored with your values intact:
  ts-runtypes enrich <source.ts> <NewName> --update
FT022Error
Stale `@rtOrphanChild` field carcass — run `ts-runtypes enrich --prune` to remove it (or restore the field).
Full build message
The reconcile commented this field out because the source type no longer
declares it. The carcass preserves your authored value inline — but a
clean, committed mirror has none.

Example:
  export const friendlyUser: FriendlyText<User> = {
-   /* @rtOrphanChild nick: {rt$label: 'Nickname'}, */
    name: {rt$label: 'Name'},
  };

Fix — if the field is really gone: `ts-runtypes enrich --prune`.
Fix — if the field was renamed, re-run `--update`; the authored value
moves to the renamed field when the ids match.
FT023Error
Unfilled blank value — a scaffolded label or message is still empty; fill in the real text.
Full build message
An empty string (`''`) at a `rt$label` / `rt$errors` slot is a generated
blank that never got authored — it ships blank to the UI wherever the
friendly text is shown, so it is exactly as incomplete as a `@todo`
marker. This is why removing the `@todo` line without filling the values
is not "done".

Example:
  export const friendlyUser: FriendlyText<User> = {
-   name: {rt$label: ''},
+   name: {rt$label: 'Name'},
  };

Fix — author the real label / message. Only the completeness gate
(`ts-runtypes enrich --require-complete`) fails on it; a plain
`--no-emit` health check reports it without failing.
GE000Error
Cannot read enrichment mirror file: {0}
Full build message
The drift check could not read this mirror file (permissions, a broken
symlink, or a race with a concurrent write).

Fix — make the file readable and re-run `ts-runtypes enrich --no-emit`.
GE001Warning
Mirror location drift — the source maps to `{0}` but this file lives at `{1}`; re-run `ts-runtypes enrich` to relocate.
Full build message
Each source file mirrors to ONE computed path per family under the
enrich root (friendly/… and mock/…, plus per-locale translation twins).
This file is not at its computed location — usually after a source move,
a genDir change, or a pre-split combined mirror that still needs
migrating.

Fix — re-run the generator; it writes the per-family files at the right
paths and migrates a legacy combined mirror:
  ts-runtypes enrich <source.ts> <Type> --update
GE002Error
Breadcrumb source `{0}` no longer exists ({1}) — the mirror is orphaned; delete it or re-run `ts-runtypes enrich`.
Full build message
The mirror's `import type { … } from '<source>'` breadcrumb resolves to
a file that is gone. Its consts describe types that no longer exist
anywhere.

Fix — if the source was deleted, delete the mirror file (both family
files and any translation twins).
Fix — if the source moved, re-run the generator from the new location
and prune the old mirror.
GE003Error
Source {0} no longer declares type `{1}` — re-run `ts-runtypes enrich`.
Full build message
The mirror imports a type name its source file no longer declares (the
type was renamed or removed). The reconcile turns its consts into
`@rtOrphan` carcasses so your authored values survive.

Fix — re-run the reconcile against the current source, then prune any
carcasses that should not come back:
  ts-runtypes enrich <source.ts> <Type> --update
  ts-runtypes enrich --prune
MD001Error
Unknown field `{0}` — the type does not declare it, so this MockData entry is dead.
Full build message
The MockData map names a field the source type does not have (removed,
renamed, or a typo). Its pool/range can never feed a generated mock.

Example — `nick` no longer exists on the type:
  interface User { name: string }
  export const mockUser: MockData<User> = {
    name: {pool: ['Ada', 'Linus']},
-   nick: {pool: ['ada99']},
  };

Fix — remove the entry, or re-run the reconcile so the mirror follows
the type:
  ts-runtypes enrich <source.ts> <Type> --update
MD011Error
Property `{0}` collides with the reserved `rt$` enrichment prefix — the type cannot be enriched.
Full build message
`rt$`-prefixed keys are reserved for enrichment meta (`rt$items`,
`rt$length`, `rt$optional`, …); a source property with that prefix is
indistinguishable from node meta, so gen refuses the type and the
MockData checker reports it here.

Fix — rename the property (a plain `$` prefix is fine; only `rt$` is
reserved):
  interface Config {
-   rt$size: number;
+   $size: number;
  }
MD020Error
Unfilled `@todo` placeholder — fill in the real sample pools/ranges, then delete the `@todo` line.
Full build message
The generator stamps a `@todo` line on every freshly-scaffolded const in
a MockData mirror file. It means "this skeleton still carries generated
blanks". A clean, committed mirror has none.

Example — a fresh scaffold:
  /** @rtType User#a1b2c3 @rtIds {name: d4e5f6} */
- // @todo: generated skeleton — fill in real data, then delete this line
  export const mockUser: MockData<User> = {
-   name: {pool: []},
+   name: {pool: ['Ada Lovelace', 'Linus Torvalds']},
  };

Fix — author realistic sample pools/ranges for the const, then delete
the whole `@todo` line (the compiler never removes it for you).
MD021Error
Stale `@rtOrphan` carcass — run `ts-runtypes enrich --prune` to remove it (or restore the type).
Full build message
The reconcile commented this MockData const out because its source type
was deleted or renamed. The carcass preserves your authored pools and
ranges so a reappearing type can restore them — but a clean, committed
mirror has none.

Fix — if the type is really gone, prune the carcass:
  ts-runtypes enrich --prune

Fix — if the type was renamed, re-run the reconcile; a matching carcass
is restored with your values intact:
  ts-runtypes enrich <source.ts> <NewName> --update
MD022Error
Stale `@rtOrphanChild` field carcass — run `ts-runtypes enrich --prune` to remove it (or restore the field).
Full build message
The reconcile commented this field out because the source type no longer
declares it. The carcass preserves your authored value inline — but a
clean, committed mirror has none.

Example:
  export const mockUser: MockData<User> = {
-   /* @rtOrphanChild nick: {pool: ['ada99']}, */
    name: {pool: ['Ada', 'Linus']},
  };

Fix — if the field is really gone: `ts-runtypes enrich --prune`.
Fix — if the field was renamed, re-run `--update`; the authored value
moves to the renamed field when the ids match.
MD023Error
Unfilled blank value — a scaffolded sample pool or range is still empty; fill in real data.
Full build message
An empty pool (`pool: []`) is a generated blank that never got authored —
it mocks nothing, so it is exactly as incomplete as a `@todo` marker.
This is why removing the `@todo` line without filling the values is not
"done".

Example:
  export const mockUser: MockData<User> = {
-   name: {pool: []},
+   name: {pool: ['Ada Lovelace', 'Linus Torvalds']},
  };

Fix — author realistic sample data. Only the completeness gate
(`ts-runtypes enrich --require-complete`) fails on it; a plain
`--no-emit` health check reports it without failing.
Copyright © 2026