ComposioHQ/composio · error · JsonSchemaRefResolutionError
JSON Schema $ref chain exceeded depth cap (${MAX_REF_CHAIN_D
Error message
JSON Schema $ref chain exceeded depth cap (${MAX_REF_CHAIN_DEPTH}): ${ref} What it means
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).
Source
Thrown at ts/packages/core/src/utils/jsonSchema.ts:240
}
}
if (!isPlainObject(node)) return node;
if (visiting.has(node)) return { ...CYCLE_BREAK_SENTINEL };
visiting.add(node);
try {
const ref = typeof node.$ref === 'string' ? node.$ref : null;
// External refs and non-$ref nodes both pass through the same clone path.
if (ref === null || !ref.startsWith('#')) {
if (ref !== null) {
// Audit signal for security-sensitive deployments: a downstream
// resolver may fetch this and trigger SSRF or local-file disclosure.
logger.warn(`Leaving external $ref untouched: ${ref}`);
}
return cloneChildren(node, visitedRefs, chainDepth, nodeDepth);
}
if (chainDepth >= MAX_REF_CHAIN_DEPTH) {
throw new JsonSchemaRefResolutionError(
`JSON Schema $ref chain exceeded depth cap (${MAX_REF_CHAIN_DEPTH}): ${ref}`,
{ meta: { ref }, possibleFixes: REF_RESOLUTION_FIXES }
);
}
if (visitedRefs.has(ref)) return { ...CYCLE_BREAK_SENTINEL };
const result = tryResolvePointer(root, ref);
let target: unknown;
if (result.kind === 'ok') {
target = result.value;
} else if (strategy === 'sentinel') {
// Lenient mode: replace the unresolved branch with the same
// permissive sentinel used for cycles, and notify the caller so
// they can emit a one-shot warn at the offending tool surface.
// The injected `description` gives the LLM an in-band signal that
// the branch is opaque; sibling-merge below will overwrite it with
// a caller-provided description if the original node has one.
onReplace?.(ref, result.reason);View on GitHub (pinned to 64b1b85502)
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.
Defensive patterns
Strategy: validation
Validate before calling
const chainLen = (s: any, seen = new Set()): number => {
if (typeof s?.$ref !== 'string' || seen.has(s)) return 0;
seen.add(s); return 1 + chainLen(resolve(s.$ref), seen);
}; Prevention
- Inline long alias chains when post-processing specs.
- Avoid aggressive ref-dedup tools on tool schemas.
When it happens
Trigger: 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.
Common situations: Aggressively deduplicated/generated OpenAPI specs, or merged specs creating long indirection chains.
Related errors
- Unsupported $ref pointer: ${pointer}
- Cannot resolve $ref ${pointer}
- JSON Schema node depth exceeded cap (${MAX_NODE_DEPTH})
- JSON Schema exceeds maximum nesting depth of ${MAX_NODE_DEPT
- Tool arguments exceed maximum nesting depth of ${MAX_NODE_DE
AI-assisted analysis of ComposioHQ/composio@64b1b85502 (2026-08-28).
Data as JSON: /api/errors/b7bc6695f510d567.
Report an issue: GitHub.