twigphp/Twig · error · SyntaxError

Unclosed comment.

Error message

Unclosed comment.

What it means

A `{# ... #}` comment (or `#{ ... #}` documentation comment) was opened but the Lexer could not find its closing `#}` before end of file. lexComment runs the comment-terminating regex; failure means the comment never terminates.

Solutions

  1. Locate the `{#` at the reported line and add the closing `#}`.
  2. Delete abandoned debug comments before committing.
  3. Remember Twig comments do not nest — remove inner `{#`/`#}` pairs when commenting out blocks that already contain comments.
  4. Add a pre-commit/CI check that parses templates to catch unclosed comments early.

Example fix

// before
{# TODO: revisit this section
<div>...</div>

// after
{# TODO: revisit this section #}
<div>...</div>
Defensive patterns

Strategy: validation

Validate before calling

if (substr_count($code, '{#') !== substr_count($code, '#}')) {
    throw new \InvalidArgumentException('Unbalanced comment markers');
}

Try / catch

try {
    $twig->parse(new \Twig\Source($code, $name));
} catch (\Twig\Error\SyntaxError $e) {
    log('Unclosed comment at line '.$e->getTemplateLine());
}

Prevention

When it happens

Trigger: A template contains `{#` with no matching `#}`, e.g. `{# TODO fix this` left in a template, or a comment whose closer was consumed by nested content like `{# a {# b #}`.

Common situations: Developers leaving debug notes and forgetting to close them; commenting out large template sections where an inner `#}` prematurely ends things then leaves the real content unclosed; comments truncated by file edits.

Related errors


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

Appendix: source

Thrown at src/Lexer.php:513

        if (isset($match[1][0])) {
            if ($this->options['whitespace_trim'] === $match[1][0]) {
                // whitespace_trim detected ({%-, {{- or {#-)
                $text = rtrim($text);
            } else {
                // whitespace_line_trim detected ({%~, {{~ or {#~)
                // don't trim \r and \n
                $text = rtrim($text, " \t\0\x0B");
            }
        }

        $this->pushToken(Token::TEXT_TYPE, $this->normalizeNewlines($text), $offset);
    }

    private function lexComment(bool $isDocumentation = false): void
    {
        $regex = $isDocumentation ? 'lex_documentation_comment' : 'lex_comment';
        if (!preg_match($this->regexes[$regex], $this->code, $match, \PREG_OFFSET_CAPTURE, $this->cursor)) {
            throw new SyntaxError('Unclosed comment.', $this->lineno, $this->source);
        }

        $comment = substr($this->code, $this->cursor, $match[0][1] - $this->cursor);
        if ($isDocumentation) {
            $documentation = $comment;
            // support a symmetric "##}" closing marker
            if (str_ends_with($documentation, '#')) {
                $documentation = substr($documentation, 0, -1);
            }
            $this->addDocumentation($documentation);
        }

        $this->moveCursor($comment.$match[0][0]);
    }

    private function lexString(): void
    {
        if (preg_match($this->regexes['interpolation_start'], $this->code, $match, 0, $this->cursor)) {

View on GitHub (pinned to a414c3a491)