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
- Fully dereference the schema first (dereferenceJsonSchema with onUnresolved: 'error', or a bundler) so dynamic-key subschemas contain no $ref
- Fix or remove the dangling $ref target from the source schema
- Check the JSON.stringify(ref) in the message to locate which pointer is unresolved
- 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
- Fully dereference schemas (onUnresolved: 'error') before dynamic-key conversion
- Bundle external refs into $defs
- Validate all $refs point to existing definitions with a lint pass
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
- Cannot resolve $ref {pointer}
- JSON Schema node depth exceeded cap ({MAX_NODE_DEPTH})
- JSON Schema $ref chain exceeded depth cap ({MAX_REF_CHAIN_DE
- JSON Schema nesting too deep to dereference
- Invalid property type {p_type}: {schema!r}
AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28).
Data as JSON: /api/errors/3ca1b89747e54a1d.
Report an issue: GitHub.