symfony/http-kernel · error · RuntimeException

Unable to process an SSI tag without a "virtual" attribute.

Error message

Unable to process an SSI tag without a "virtual" attribute.

What it means

During SSI response processing (only for configured content types), the body is split on <!--#include ... --> comments and attributes parsed by regex instead of a real XML parser. When a matched include directive lacks the "virtual" attribute, no include target can be determined, so this validation guard fires. It indicates malformed SSI markup in the response — an SSI include directive written without virtual, or with unrecognized attribute syntax.

Solutions

  1. Add virtual="..." to the SSI include tag.
  2. Verify the tag uses SSI syntax (virtual) not ESI syntax (src).
  3. Remove the tag if the include is not needed.

Example fix

// before
<!--#include set="box" -->
// after
<!--#include virtual="/_fragment/news" -->
Defensive patterns

Strategy: validation

Validate before calling

if (preg_match('/<!--#include((?![^>]*virtual=)[^>]*)-->/', $html)) { // fix SSI tags missing virtual }

Prevention

When it happens

Trigger: HTML containing <!--#include ...--> without virtual=, processed on a text/html response with SSI enabled.

Common situations: Migrating ESI tags to SSI (src vs virtual mismatch), or hand-written SSI comments missing the attribute.

Understand the failure class

Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.

Related errors


AI-assisted analysis of symfony/http-kernel@aa3a39d728 (2026-09-13). Data as JSON: /api/errors/e61ed98c4b9e3e19. Report an issue: GitHub.

Appendix: source

Thrown at HttpCache/Ssi.php:67

        if (!\in_array($parts[0], $this->contentTypes, true)) {
            return $response;
        }

        // we don't use a proper XML parser here as we can have SSI tags in a plain text response
        $content = $response->getContent();
        $boundary = self::generateBodyEvalBoundary();
        $chunks = preg_split('#<!--\#include\s+(.*?)\s*-->#', $content, -1, \PREG_SPLIT_DELIM_CAPTURE);

        $i = 1;
        while (isset($chunks[$i])) {
            $options = [];
            preg_match_all('/(virtual)="([^"]*?)"/', $chunks[$i], $matches, \PREG_SET_ORDER);
            foreach ($matches as $set) {
                $options[$set[1]] = $set[2];
            }

            if (!isset($options['virtual'])) {
                throw new \RuntimeException('Unable to process an SSI tag without a "virtual" attribute.');
            }

            $chunks[$i] = $boundary.$options['virtual']."\n\n\n";
            $i += 2;
        }
        $content = $boundary.implode('', $chunks).$boundary;

        $response->setContent($content);
        $response->headers->set('X-Body-Eval', 'SSI');

        // remove SSI/1.0 from the Surrogate-Control header
        $this->removeFromControl($response);

        return $response;
    }
}

View on GitHub (pinned to aa3a39d728)