emberjs/ember.js · error · Error

infinite rendering invalidation detected

Error message

infinite rendering invalidation detected

What it means

During a Backburner loop-end flush, Ember re-validates renderers; if a renderer remains invalid after the rerender loop limit (ENV._RERENDER_LOOP_LIMIT), invalidation never converges, so Ember destroys the renderer and throws. This guards against endless revalidation (typically CP churn).

Source

Thrown at packages/@ember/-internals/glimmer/lib/base-renderer.ts:208

function resolveRenderPromise() {
  if (renderSettledDeferred !== null) {
    let resolve = renderSettledDeferred.resolve;
    renderSettledDeferred = null;

    _backburner.join(null, resolve);
  }
}

let loops = 0;
function loopEnd() {
  for (let renderer of renderers) {
    if (!renderer.isValid()) {
      if (loops > ENV._RERENDER_LOOP_LIMIT) {
        loops = 0;
        // TODO: do something better
        renderer.destroy();
        throw new Error('infinite rendering invalidation detected');
      }
      loops++;
      return _backburner.join(null, NO_OP);
    }
  }
  loops = 0;
  resolveRenderPromise();
}

_backburner.on('begin', loopBegin);
_backburner.on('end', loopEnd);

type Resolver = ClassicResolver;

interface RendererData {
  owner: object;
  context: EvaluationContext;
  builder: IBuilder;

View on GitHub (pinned to 26f97246a8)

Solutions

  1. Find the self-invalidating computed/tracked property (log reads/writes) and make it stable (cache the array/object)
  2. Avoid mutating observed/tracked state inside getters or during render
  3. Break mutual dependencies between computeds
  4. Temporarily raise ENV._RERENDER_LOOP_LIMIT only to diagnose, not to fix

Example fix

// before
get items() {
  return this.model.filter(x => x.active).map(x => ({ ...x })); // new array every access
}
// after
get items() {
  return this.model.filter(x => x.active); // or memoize tracked state
}
Defensive patterns

Strategy: validation

Validate before calling

// detect self-invalidating computeds in dev
get foo() { assert('no writes in getters', !this._writing); }

Try / catch

try { renderApp(); } catch (e) { if (e.message === 'infinite rendering invalidation detected') { /* inspect recently-read tracked/computed props */ } throw e; }

Prevention

When it happens

Trigger: Computed properties or observers whose getters invalidate themselves or each other; a template depending on a value that changes every time it's read; re-render loop exceeding the limit.

Common situations: A getter returning a new array/object each access feeding a tracked dependency; mutually dependent computeds; mutating state during render; ember-metal observer cycles after upgrades.

Related errors


AI-assisted analysis of emberjs/ember.js@26f97246a8 (2026-09-01). Data as JSON: /api/errors/fec7c69134d5a400. Report an issue: GitHub.