ComposioHQ/composio · error · Error

Unresolved $ref ${JSON.stringify(ref)} in a dynamic-key sche

Error message

Unresolved $ref ${JSON.stringify(ref)} in a dynamic-key schema.

What it means

assertDynamicReferencesResolve walks every $ref reachable from dynamic-key subschemas; if a reference (after __absolute_ref__ normalization) has no target in the lookup table, it throws — dynamic keys cannot be resolved lazily like static ones, so dangling refs must fail conversion up front.

Source

Thrown at ts/packages/json-schema-to-effect-schema/src/index.ts:248

// Follows every reference reachable from one dynamic-key subschema, including
// through the schemas those references resolve to, so a local pointer whose
// target carries a dangling reference is caught as well.
const assertDynamicReferencesResolve = (
  subSchema: unknown,
  lookup: Record<string, unknown>,
  seen: WeakSet<object>
): void => {
  forEachSchemaNode(subSchema, seen, node => {
    const ref = node.$ref;
    if (typeof ref !== 'string') {
      return;
    }

    const absolute = (node as { readonly __absolute_ref__?: string }).__absolute_ref__ ?? ref;
    const target = lookup[absolute];
    if (target === undefined) {
      throw new Error(`Unresolved $ref ${JSON.stringify(ref)} in a dynamic-key schema.`);
    }

    assertDynamicReferencesResolve(target, lookup, seen);
  });
};

// `@cfworker/json-schema` compiles `patternProperties` keys and resolves `$ref`
// lazily, inside `validate`. Left alone, a defect in the schema surfaces once
// per call through the same channel as a genuine input failure, telling the
// caller their arguments are wrong when the tool's schema is what is broken.
// Checking eagerly moves those defects to construction, where the CLI already
// reports them as a compile failure against the cached schema path.
//
// The scope matches what the Python SDK rejects while wrapping a tool: the keys
// of `patternProperties`, and references inside a dynamic-key subschema. A
// reference in a declared property is deliberately left to the interpreter —
// widening past parity would reject tool schemas that validate today.
const assertSchemaIsInterpretable = (schema: InterpreterSchema): void => {

View on GitHub (pinned to 64b1b85502)

Solutions

  1. Fully dereference the schema first (dereferenceJsonSchema with onUnresolved: 'error', or a bundler) so dynamic-key subschemas contain no $ref
  2. Fix or remove the dangling $ref target from the source schema
  3. Check the JSON.stringify(ref) in the message to locate which pointer is unresolved
  4. If the ref is external, inline its target definition into $defs/definitions before converting

Example fix

// before
{ "type": "object", "additionalProperties": { "$ref": "#/definitions/entry" } } // 'entry' missing
// after
{ "type": "object", "additionalProperties": { "type": "string" }, "definitions": { "entry": { "type": "string" } } }
Defensive patterns

Strategy: validation

Validate before calling

function refsResolve(root: any): string[] {
  const missing: string[] = [];
  const visit = (n: any) => {
    if (!n || typeof n !== 'object') return;
    if (typeof n.$ref === 'string') {
      const ptr = n.$ref.slice(1).split('/');
      let t: any = root;
      for (const k of ptr) t = t?.[k];
      if (t === undefined) missing.push(n.$ref);
    }
    Object.values(n).forEach(visit);
  };
  visit(root);
  return missing;
}
const missing = refsResolve(schema);
if (missing.length) throw new Error(`Unresolved refs: ${missing.join(', ')}`);

Try / catch

try { convert(schema); } catch (e) { if (/Unresolved \$ref/.test((e as Error).message)) { schema = dereferenceJsonSchema(schema, { onUnresolved: 'error' }); } throw e; }

Prevention

When it happens

Trigger: A JSON Schema with additionalProperties/patternProperties dynamic subschemas containing a $ref to '#/definitions/Missing' or an external ref that was not bundled/dereferenced before conversion (dereferenceJsonSchema with onUnresolved: 'sentinel' leaves sentinels that then fail here in dynamic positions).

Common situations: Passing raw OpenAPI schemas with un-inlined refs; partial $ref dereferencing; refs pointing to definitions omitted during schema trimming; sentinel-on-unresolved mode leaking sentinels into dynamic keys.

Related errors


AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28). Data as JSON: /api/errors/3ca1b89747e54a1d. Report an issue: GitHub.