{"record":{"id":"b7bc6695f510d567","repo":"ComposioHQ/composio","slug":"json-schema-ref-chain-exceeded-depth-cap-max-r","errorCode":null,"errorMessage":"JSON Schema $ref chain exceeded depth cap (${MAX_REF_CHAIN_DEPTH}): ${ref}","messagePattern":"JSON Schema \\$ref chain exceeded depth cap \\((.+?)\\): (.+?)","errorType":"exception","errorClass":"JsonSchemaRefResolutionError","httpStatus":null,"severity":"error","filePath":"ts/packages/core/src/utils/jsonSchema.ts","lineNumber":240,"sourceCode":"      }\n    }\n    if (!isPlainObject(node)) return node;\n    if (visiting.has(node)) return { ...CYCLE_BREAK_SENTINEL };\n    visiting.add(node);\n    try {\n      const ref = typeof node.$ref === 'string' ? node.$ref : null;\n      // External refs and non-$ref nodes both pass through the same clone path.\n      if (ref === null || !ref.startsWith('#')) {\n        if (ref !== null) {\n          // Audit signal for security-sensitive deployments: a downstream\n          // resolver may fetch this and trigger SSRF or local-file disclosure.\n          logger.warn(`Leaving external $ref untouched: ${ref}`);\n        }\n        return cloneChildren(node, visitedRefs, chainDepth, nodeDepth);\n      }\n\n      if (chainDepth >= MAX_REF_CHAIN_DEPTH) {\n        throw new JsonSchemaRefResolutionError(\n          `JSON Schema $ref chain exceeded depth cap (${MAX_REF_CHAIN_DEPTH}): ${ref}`,\n          { meta: { ref }, possibleFixes: REF_RESOLUTION_FIXES }\n        );\n      }\n      if (visitedRefs.has(ref)) return { ...CYCLE_BREAK_SENTINEL };\n\n      const result = tryResolvePointer(root, ref);\n      let target: unknown;\n      if (result.kind === 'ok') {\n        target = result.value;\n      } else if (strategy === 'sentinel') {\n        // Lenient mode: replace the unresolved branch with the same\n        // permissive sentinel used for cycles, and notify the caller so\n        // they can emit a one-shot warn at the offending tool surface.\n        // The injected `description` gives the LLM an in-band signal that\n        // the branch is opaque; sibling-merge below will overwrite it with\n        // a caller-provided description if the original node has one.\n        onReplace?.(ref, result.reason);","sourceCodeStart":222,"sourceCodeEnd":258,"githubUrl":"https://github.com/ComposioHQ/composio/blob/64b1b85502b1beeb2379e6c9e8bf1104504fa637/ts/packages/core/src/utils/jsonSchema.ts#L222-L258","documentation":"While following a chain of $ref → $ref → $ref, walk() caps chain length at MAX_REF_CHAIN_DEPTH. A chain longer than the cap throws instead of resolving (cycles are separately broken with a sentinel, so this is about genuinely long acyclic chains).","triggerScenarios":"Schemas where resolving one $ref yields another $ref many times in a row — e.g. aliases of aliases (A→B→C→…→Z) or spec-minifiers that replace every inline node with a ref.","commonSituations":"Aggressively deduplicated/generated OpenAPI specs, or merged specs creating long indirection chains.","solutions":["Inline some of the intermediate refs to shorten the chain.","Merge/dedupe redundant alias definitions in the source spec.","Regenerate the schema with less aggressive $ref compression."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"const chainLen = (s: any, seen = new Set()): number => {\n  if (typeof s?.$ref !== 'string' || seen.has(s)) return 0;\n  seen.add(s); return 1 + chainLen(resolve(s.$ref), seen);\n};","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Inline long alias chains when post-processing specs.","Avoid aggressive ref-dedup tools on tool schemas."],"tags":["json-schema","ref-resolution","depth-limit","typescript"],"backgroundTag":"json-schema-ref-resolution-failed","analyzedSha":"64b1b85502b1beeb2379e6c9e8bf1104504fa637","analyzedAt":"2026-08-28T15:39:33.623Z","schemaVersion":2},"datasetVersion":"2026-08-28T16:17:29.566Z"}