chartjs/Chart.js · error · Error

Recursion detected: ${Array.from(_stack).join('->')}->${prop

Error message

Recursion detected: ${Array.from(_stack).join('->')}->${prop}

What it means

When a Chart.js option is a scriptable function, the config resolver invokes it and tracks the property name in a per-context _stack to detect cycles. If resolving a scriptable option references itself (directly or through sub-resolvers), _stack.has(prop) becomes true and the resolver throws to prevent infinite recursion / stack overflow. This protects option resolution for scriptable values that read other options via the resolver proxy.

Source

Thrown at src/helpers/helpers.config.ts:263

  if (isArray(value) && value.length) {
    value = _resolveArray(prop, value, target, descriptors.isIndexable);
  }
  if (needsSubResolver(prop, value)) {
    // if the resolved value is an object, create a sub resolver for it
    value = _attachContext(value, _context, _subProxy && _subProxy[prop], descriptors);
  }
  return value;
}

function _resolveScriptable(
  prop: string,
  getValue: (ctx: AnyObject, sub: AnyObject) => unknown,
  target: ContextCache,
  receiver: AnyObject
) {
  const {_proxy, _context, _subProxy, _stack} = target;
  if (_stack.has(prop)) {
    throw new Error('Recursion detected: ' + Array.from(_stack).join('->') + '->' + prop);
  }
  _stack.add(prop);
  let value = getValue(_context, _subProxy || receiver);
  _stack.delete(prop);
  if (needsSubResolver(prop, value)) {
    // When scriptable option returns an object, create a resolver on that.
    value = createSubResolver(_proxy._scopes, _proxy, prop, value);
  }
  return value;
}

function _resolveArray(
  prop: string,
  value: unknown[],
  target: ContextCache,
  isIndexable: (key: string) => boolean
) {
  const {_proxy, _context, _subProxy, _descriptors: descriptors} = target;

View on GitHub (pinned to cb02e1d207)

Solutions

  1. Read the value from a DIFFERENT, non-scriptable option or from the raw data/context instead of re-entering the same key.
  2. Hoist the constant out of the function: compute the base value once outside the scriptable closure.
  3. Break the cycle by accessing ctx.parsed / ctx.dataset / raw data rather than ctx.chart.options.<sameKey>.
  4. Audit the resolution chain shown in the message (a->b->...->prop) and remove the back-reference.

Example fix

// before - borderColor reads itself via options -> recursion
options.borderColor = (ctx) => ctx.chart.options.borderColor;

// after - read from a separate config value or data
options.borderColor = (ctx) => ctx.dataset.borderColor || '#000';
Defensive patterns

Strategy: try-catch

Validate before calling

// Pre-scan scriptable options for self-references before applying the config (heuristic).
function detectSelfReference(optionFns) {
  for (const [key, fn] of Object.entries(optionFns)) {
    const src = String(fn);
    // crude check: does the function body re-read the same option key?
    if (new RegExp(`\\boptions\\.${key}\\b`).test(src)) {
      console.warn(`Scriptable option '${key}' appears to reference itself; this may recurse.`);
    }
  }
}

Try / catch

// Wrap option resolution in tests to surface recursion as a readable failure.
try {
  chart.update();
} catch (e) {
  if (/Recursion detected/.test(String(e?.message))) {
    console.error('Scriptable option cycle:', e.message);
    // strip scriptable functions and retry to isolate the offender
  } else {
    throw e;
  }
}

Prevention

When it happens

Trigger: A scriptable option function that reads the same option (or a chain leading back to it) from the context/sub-proxy, e.g. options.borderColor = (ctx) => ctx.chart.options.borderColor; returning an object that re-triggers resolution of the same key; mutual references between two scriptable options.

Common situations: Writing a scriptable color/size that references ctx.chart.options.X where X is the same option; spreading the resolver into itself; sub-options that loop back through needsSubResolver; misusing the scriptable context's subProxy to read parent options that recurse.

Related errors


AI-assisted analysis of chartjs/Chart.js@cb02e1d207 (2026-08-12). Data as JSON: /api/errors/96870d29ba38ba4f. Report an issue: GitHub.