{"record":{"id":"5b0c40f6d825002f","repo":"twigphp/Twig","slug":"the-tag-of-a-node-can-only-be-set-once","errorCode":null,"errorMessage":"The tag of a node can only be set once.","messagePattern":"The tag of a node can only be set once\\.","errorType":"exception","errorClass":"LogicException","httpStatus":null,"severity":"error","filePath":"src/Node/Node.php","lineNumber":159,"sourceCode":"    {\n        return $this->documentation;\n    }\n\n    /**\n     * @internal\n     */\n    public function setDocumentation(?string $documentation): void\n    {\n        $this->documentation = $documentation;\n    }\n\n    /**\n     * @internal\n     */\n    public function setNodeTag(string $tag): void\n    {\n        if ($this->tag) {\n            throw new \\LogicException('The tag of a node can only be set once.');\n        }\n\n        $this->tag = $tag;\n    }\n\n    public function hasAttribute(string $name): bool\n    {\n        return \\array_key_exists($name, $this->attributes);\n    }\n\n    public function getAttribute(string $name)\n    {\n        if (!\\array_key_exists($name, $this->attributes)) {\n            throw new \\LogicException(\\sprintf('Attribute \"%s\" does not exist for Node \"%s\".', $name, static::class));\n        }\n\n        $triggerDeprecation = \\func_num_args() > 1 ? func_get_arg(1) : true;\n        if ($triggerDeprecation && isset($this->attributeNameDeprecations[$name])) {","sourceCodeStart":141,"sourceCodeEnd":177,"githubUrl":"https://github.com/twigphp/Twig/blob/a414c3a491defb5a60f2fc88ef79ff37c90010cd/src/Node/Node.php#L141-L177","documentation":"Node::setNodeTag() is an internal one-shot API: each node can receive its \"tag\" (the template tag like `if`, `for`, `block`) exactly once. Calling it a second time on an already-tagged node is a LogicException because it indicates a parser/visitor bug or duplicate tagging, not user-input validation. Twig throws this to enforce the invariant that a node's tag is immutable after assignment.","triggerScenarios":"Calling setNodeTag() twice on the same Node instance, e.g. a custom NodeVisitor that assigns a tag in enterNode() for every traversal pass, or subparse()/the parser re-visiting a node and re-tagging it. Any custom code path that re-enters subparse() with cached Node objects.","commonSituations":"Writing a custom Twig extension whose node visitor sets tags on nodes without checking hasNodeTag() first; running the parser over the same node twice (double compilation passes); caching Node instances across template compilations and re-running the parser on them.","solutions":["Guard the call with $node->hasNodeTag() (or check a local flag) before calling setNodeTag().","Ensure your NodeVisitor only sets the tag once, e.g. set it in enterNode() and track visited nodes in the visitor state.","Do not cache or reuse Node instances between parse/compile runs; create fresh nodes per compilation.","Update the Twig version if this fires during normal core parsing (a known core/extension incompatibility)."],"exampleFix":"// before\npublic function enterNode(\\Twig\\Node\\Node $node, Environment $env): Node\n{\n    $node->setNodeTag('custom');\n    return $node;\n}\n// after\npublic function enterNode(\\Twig\\Node\\Node $node, Environment $env): Node\n{\n    if (!$node->hasNodeTag()) {\n        $node->setNodeTag('custom');\n    }\n    return $node;\n}","handlingStrategy":"validation","validationCode":"if (!$node->hasNodeTag()) {\n    $node->setNodeTag('custom');\n}","typeGuard":null,"tryCatchPattern":"try {\n    $node->setNodeTag($tag);\n} catch (\\LogicException $e) {\n    // tag already set; treat as no-op\n}","preventionTips":["Always check hasNodeTag() before setNodeTag().","Set tags in exactly one place (e.g. a single enterNode() branch) in visitors.","Never reuse Node instances across parse/compile passes."],"tags":["twig","parser","node-visitor","logic-exception"],"backgroundTag":"invalid-state-transition","analyzedSha":"a414c3a491defb5a60f2fc88ef79ff37c90010cd","analyzedAt":"2026-09-13T15:10:46.849Z","contentChangedAt":"2026-09-13T15:10:46.849Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}