symfony/http-kernel · error · RuntimeException
Unable to process an ESI tag without a "src" attribute.
Error message
Unable to process an ESI tag without a "src" attribute.
What it means
During ESI response processing, the response body is split on esi:include tags and their attributes parsed with regex (no XML parser, since ESI tags may appear in plain text). When a matched esi:include tag has no "src" attribute, there is no resource to fetch, so this validation guard fires. It indicates malformed ESI markup in the upstream response — an esi:include written without src (or with attributes the regex parser does not recognize).
Solutions
- Add a src attribute to every <esi:include> tag.
- Check templates for attribute typos (src, not href/url).
- Remove malformed esi tags if they are not intended to be processed.
Example fix
// before <esi:include alt="fallback" /> // after <esi:include src="/_fragment/news" alt="fallback" />
Defensive patterns
Strategy: validation
Validate before calling
if (str_contains($html, '<esi:include') && !preg_match('/<esi:include[^>]+src="[^"]+"/', $html)) { // fix templates } Prevention
- Template lint for ESI tags requiring src
- Use a Twig helper to generate esi tags
- Add integration tests rendering pages with ESI includes
When it happens
Trigger: HTML content containing an <esi:include> tag lacking src=; process() is called on a text/html response containing such a tag.
Common situations: Hand-written or template-generated ESI tags with a typo'd attribute (href instead of src), or empty src stripped by templating.
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
- Error when rendering
- Unable to process an SSI tag without a "virtual" attribute.
- Unable to create the store directory
- Passing non-scalar values as part of URI attributes to the…
- The " " renderer does not exist.
AI-assisted analysis of symfony/http-kernel@aa3a39d728 (2026-09-13).
Data as JSON: /api/errors/84d115b8dde445a8.
Report an issue: GitHub.
Appendix: source
Thrown at HttpCache/Esi.php:86
// we don't use a proper XML parser here as we can have ESI tags in a plain text response
$content = $response->getContent();
$content = preg_replace('#<esi\:remove>.*?</esi\:remove>#s', '', $content);
$content = preg_replace('#<esi\:comment[^>]+>#s', '', $content);
$boundary = self::generateBodyEvalBoundary();
$chunks = preg_split('#<esi\:include\s+(.*?)\s*(?:/|</esi\:include)>#', $content, -1, \PREG_SPLIT_DELIM_CAPTURE);
$i = 1;
while (isset($chunks[$i])) {
$options = [];
preg_match_all('/(src|onerror|alt)="([^"]*?)"/', $chunks[$i], $matches, \PREG_SET_ORDER);
foreach ($matches as $set) {
$options[$set[1]] = $set[2];
}
if (!isset($options['src'])) {
throw new \RuntimeException('Unable to process an ESI tag without a "src" attribute.');
}
$chunks[$i] = $boundary.$options['src']."\n".($options['alt'] ?? '')."\n".('continue' === ($options['onerror'] ?? ''))."\n";
$i += 2;
}
$content = $boundary.implode('', $chunks).$boundary;
$response->setContent($content);
$response->headers->set('X-Body-Eval', 'ESI');
// remove ESI/1.0 from the Surrogate-Control header
$this->removeFromControl($response);
return $response;
}
}
View on GitHub (pinned to aa3a39d728)