twigphp/Twig · error · LogicException

Circular template inheritance detected while building a…

Error message

Circular template inheritance detected while building a block chain from "%s".

What it means

Twig throws this LogicException when resolving the parent chain of templates (a 'block chain') encounters a cycle, i.e. a template whose getParent() eventually returns a template already seen in the chain. Template inheritance must form a finite linear chain; a cycle would loop forever, so Twig aborts eagerly in resolveLineage.

Solutions

  1. Inspect the template named in the message and trace its {% extends %} chain; remove the cycle so each template extends a strict ancestor-free parent.
  2. If the parent is dynamic, add a guard so the evaluated parent never equals or extends the current template.
  3. Log or dump the resolved parent value to find where the variable pointing back to the template comes from.
  4. Restructure shared blocks into a common base template instead of cross-extending templates.

Example fix

// before
{# base.html.twig #}
{% extends dynamic_parent %} {# dynamic_parent resolves to 'base.html.twig' itself #}

// after
{% if dynamic_parent != 'base.html.twig' %}
{% extends dynamic_parent %}
{% endif %}
Defensive patterns

Strategy: validation

Validate before calling

function validatesLineage(\Twig\Environment $env, string $name): bool {
    $visited = [];
    $tpl = $env->load($name)->unwrap();
    while (false !== $tpl) {
        $id = spl_object_id($tpl);
        if (isset($visited[$id])) { return false; }
        $visited[$id] = true;
        $tpl = $tpl->getParent([]);
    }
    return true;
}

Try / catch

try { $twig->render($template, $vars); } catch (\LogicException $e) { if (str_contains($e->getMessage(), 'Circular template inheritance')) { log('cycle in extends chain', ['template' => $template]); } throw $e; }

Prevention

When it happens

Trigger: A template's parent expression resolves back to itself or an ancestor — e.g. {% extends self %}-style dynamic parents, a dynamic parent like {% extends someVar %} where someVar names the same or an ancestor template, or two templates extending each other. Triggered when a template using blocks is loaded via resolveBlocks -> resolveLineage.

Common situations: Dynamic parent expressions built from variables or config whose values were computed incorrectly; refactoring that accidentally makes template A extend B while B extends A; programmatic template loading where the 'parent' name is built from user data that points back to the same template.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of twigphp/Twig@a414c3a491 (2026-09-13). Data as JSON: /api/errors/c17e826959a58169. Report an issue: GitHub.

Appendix: source

Thrown at src/BlockChain.php:168

        return $this->blocks = $blocks;
    }

    /**
     * @param array<string, mixed> $context
     *
     * @return array{list<Template>, bool}
     */
    private function resolveLineage(array $context): array
    {
        $lineage = [];
        $fixed = true;

        foreach ($this->templates as $template) {
            $seen = [];
            do {
                if (isset($seen[$id = spl_object_id($template)])) {
                    throw new \LogicException(\sprintf('Circular template inheritance detected while building a block chain from "%s".', $template->getTemplateName()));
                }
                $seen[$id] = true;
                $lineage[] = $template;

                $parent = $template->getParent($context);
                $fixed = $fixed && $template->hasFixedParent();

                // a dynamic parent expression can evaluate to a template from another environment
                $template = $parent instanceof TemplateWrapper ? $parent->unwrap() : $parent;
                if (false !== $template && !$template->isOwnedBy($this->env)) {
                    throw new \LogicException('A block chain cannot contain templates from different Twig environments.');
                }
            } while (false !== $template);
        }

        return [$lineage, $fixed];
    }

View on GitHub (pinned to a414c3a491)