{"record":{"id":"3ca1b89747e54a1d","repo":"ComposioHQ/composio","slug":"unresolved-ref-json-stringify-ref-in-a-dynami","errorCode":null,"errorMessage":"Unresolved $ref ${JSON.stringify(ref)} in a dynamic-key schema.","messagePattern":"Unresolved \\$ref (.+?) in a dynamic-key schema\\.","errorType":"validation","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"ts/packages/json-schema-to-effect-schema/src/index.ts","lineNumber":248,"sourceCode":"\n// Follows every reference reachable from one dynamic-key subschema, including\n// through the schemas those references resolve to, so a local pointer whose\n// target carries a dangling reference is caught as well.\nconst assertDynamicReferencesResolve = (\n  subSchema: unknown,\n  lookup: Record<string, unknown>,\n  seen: WeakSet<object>\n): void => {\n  forEachSchemaNode(subSchema, seen, node => {\n    const ref = node.$ref;\n    if (typeof ref !== 'string') {\n      return;\n    }\n\n    const absolute = (node as { readonly __absolute_ref__?: string }).__absolute_ref__ ?? ref;\n    const target = lookup[absolute];\n    if (target === undefined) {\n      throw new Error(`Unresolved $ref ${JSON.stringify(ref)} in a dynamic-key schema.`);\n    }\n\n    assertDynamicReferencesResolve(target, lookup, seen);\n  });\n};\n\n// `@cfworker/json-schema` compiles `patternProperties` keys and resolves `$ref`\n// lazily, inside `validate`. Left alone, a defect in the schema surfaces once\n// per call through the same channel as a genuine input failure, telling the\n// caller their arguments are wrong when the tool's schema is what is broken.\n// Checking eagerly moves those defects to construction, where the CLI already\n// reports them as a compile failure against the cached schema path.\n//\n// The scope matches what the Python SDK rejects while wrapping a tool: the keys\n// of `patternProperties`, and references inside a dynamic-key subschema. A\n// reference in a declared property is deliberately left to the interpreter —\n// widening past parity would reject tool schemas that validate today.\nconst assertSchemaIsInterpretable = (schema: InterpreterSchema): void => {","sourceCodeStart":230,"sourceCodeEnd":266,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/ts/packages/json-schema-to-effect-schema/src/index.ts#L230-L266","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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"],"exampleFix":"// before\n{ \"type\": \"object\", \"additionalProperties\": { \"$ref\": \"#/definitions/entry\" } } // 'entry' missing\n// after\n{ \"type\": \"object\", \"additionalProperties\": { \"type\": \"string\" }, \"definitions\": { \"entry\": { \"type\": \"string\" } } }","handlingStrategy":"validation","validationCode":"function refsResolve(root: any): string[] {\n  const missing: string[] = [];\n  const visit = (n: any) => {\n    if (!n || typeof n !== 'object') return;\n    if (typeof n.$ref === 'string') {\n      const ptr = n.$ref.slice(1).split('/');\n      let t: any = root;\n      for (const k of ptr) t = t?.[k];\n      if (t === undefined) missing.push(n.$ref);\n    }\n    Object.values(n).forEach(visit);\n  };\n  visit(root);\n  return missing;\n}\nconst missing = refsResolve(schema);\nif (missing.length) throw new Error(`Unresolved refs: ${missing.join(', ')}`);","typeGuard":null,"tryCatchPattern":"try { convert(schema); } catch (e) { if (/Unresolved \\$ref/.test((e as Error).message)) { schema = dereferenceJsonSchema(schema, { onUnresolved: 'error' }); } throw e; }","preventionTips":["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"],"tags":["json-schema","refs","dereferencing","effect"],"backgroundTag":"unresolved-schema-ref","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}