can1357/oh-my-pi · error · OmpTypeError

unresolved $ref: ${ref}

Error message

unresolved $ref: ${ref}

What it means

A `$ref` matched the `#/$defs/Name` or `#/definitions/Name` shape, but the named definition does not exist (or the defs container itself is missing/not an object). This is a dangling reference: the schema points at a definition that was never declared.

Source

Thrown at packages/omptype/src/from-json-schema.ts:52

	readonly #aliases = new Map<string, IR>();

	constructor(root: JsonSchema) {
		this.#root = root;
	}

	resolveRef(ref: string): IR {
		const cached = this.#aliases.get(ref);
		if (cached !== undefined) return cached;

		let target: unknown;
		if (ref === "#") {
			target = this.#root;
		} else {
			const defsMatch = /^#\/(\$defs|definitions)\/(.+)$/.exec(ref);
			if (defsMatch === null) throw new OmpTypeError(`unsupported $ref: ${ref}`);
			const defs = this.#root[defsMatch[1]];
			target = typeof defs === "object" && defs !== null ? (defs as JsonSchema)[defsMatch[2]] : undefined;
			if (target === undefined) throw new OmpTypeError(`unresolved $ref: ${ref}`);
		}

		// Register the alias before lowering so recursive references resolve
		// to the same node instead of recursing forever.
		let lowered: IR | undefined;
		const alias: IR = {
			k: "alias",
			name: ref,
			resolve: () => {
				lowered ??= this.lower(target);
				return lowered;
			},
		};
		this.#aliases.set(ref, alias);
		return alias;
	}

	lower(schema: unknown): IR {

View on GitHub (pinned to 9690622007)

Solutions

  1. Add the missing definition under `$defs` (or `definitions`) with the exact referenced name.
  2. Fix the typo / casing in the `$ref` string to match an existing definition.
  3. Validate the schema with a JSON Schema validator before conversion to catch dangling refs.

Example fix

// before
{ "type": "object", "properties": { "id": { "$ref": "#/$defs/Id" } } } // no $defs
// after
{ "type": "object", "properties": { "id": { "$ref": "#/$defs/Id" } }, "$defs": { "Id": { "type": "string" } } }
Defensive patterns

Strategy: validation

Validate before calling

function refsResolve(schema: Record<string, unknown>): boolean {
  const defs = (schema.$defs ?? schema.definitions) as Record<string, unknown> | undefined;
  const walk = (node: unknown): boolean => {
    if (typeof node !== "object" || node === null) return true;
    const ref = (node as { $ref?: unknown }).$ref;
    if (typeof ref === "string") {
      const m = /^#\/(?:\$defs|definitions)\/(.+)$/.exec(ref);
      if (m && (!defs || !(m[1] in defs))) return false;
    }
    return Object.values(node).every(walk);
  };
  return walk(schema);
}

Try / catch

try {
  const t = fromJsonSchema(schema);
} catch (err) {
  if (err instanceof Error && err.message.startsWith("unresolved $ref")) {
    throw new Error(`Add missing definition: ${err.message}`);
  }
  throw err;
}

Prevention

When it happens

Trigger: `{"$ref": "#/$defs/Missing"}` when `$defs` is empty, omitted, or lacks the key `Missing`; typo in the definition name; def stripped by a bundler/pruner.

Common situations: Hand-edited schemas with typos; code-generated schemas referencing defs that were filtered out; case-sensitivity mismatches (`#/$defs/user` vs `User`).

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/1be6a97bd1d7a7f0. Report an issue: GitHub.