symfony/http-kernel · error · RuntimeException

A restored response must have the X-Content-Digest header.

Error message

A restored response must have the X-Content-Digest header.

What it means

HttpCache Store::write persists a response; if the response already carries an X-Body-File header (meaning its body was previously restored from disk), it must also carry an X-Content-Digest header identifying the stored body. This RuntimeException is a safeguard against a corrupted or hand-crafted restored response being re-stored without its content digest, which would break cache consistency between the metadata file and the body file on disk.

Solutions

  1. Ensure any response carrying X-Body-File also carries the matching X-Content-Digest header before calling write()
  2. Never manually set or strip X-Body-File / X-Content-Digest headers; let Store::restore() create them
  3. If building a fresh Response to cache, remove the X-Body-File header so the store writes the body normally

Example fix

// before
$response->headers->set('X-Body-File', $path);
$store->write($request, $response);
// after
$response->headers->remove('X-Body-File'); // let the store recompute digest + body file
$store->write($request, $response);
Defensive patterns

Strategy: validation

Validate before calling

if ($response->headers->has('X-Body-File') && !$response->headers->has('X-Content-Digest')) {
    throw new \RuntimeException('Refusing to store: X-Body-File without X-Content-Digest');
}
$store->write($request, $response);

Try / catch

try {
    $store->write($request, $response);
} catch (\RuntimeException $e) {
    $response->headers->remove('X-Body-File');
    $response->headers->remove('X-Content-Digest');
    $store->write($request, $response);
}

Prevention

When it happens

Trigger: Calling HttpCache's Store::write() (via the HttpCache kernel) with a Response whose headers contain X-Body-File but not X-Content-Digest — e.g. custom code that manually sets X-Body-File to trick the store into skipping a body rewrite, or a subclass of Store that builds such responses.

Common situations: Custom HttpCache Store subclasses that copy/clone cached responses and strip internal headers; debugging code that mutates internal X-* headers; restoring a response manually from cache files with an incomplete header set.

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 symfony/http-kernel@aa3a39d728 (2026-09-13). Data as JSON: /api/errors/17cd140aee13db88. Report an issue: GitHub.

Appendix: source

Thrown at HttpCache/Store.php:186

    }

    /**
     * Writes a cache entry to the store for the given Request and Response.
     *
     * Existing entries are read and any that match the response are removed. This
     * method calls write with the new list of cache entries.
     *
     * @throws \RuntimeException
     */
    public function write(Request $request, Response $response): string
    {
        $key = $this->getCacheKey($request);
        $storedEnv = $this->persistRequest($request);

        if ($response->headers->has('X-Body-File')) {
            // Assume the response came from disk, but at least perform some safeguard checks
            if (!$response->headers->has('X-Content-Digest')) {
                throw new \RuntimeException('A restored response must have the X-Content-Digest header.');
            }

            $digest = $response->headers->get('X-Content-Digest');
            if ($this->getPath($digest) !== $response->headers->get('X-Body-File')) {
                throw new \RuntimeException('X-Body-File and X-Content-Digest do not match.');
            }
        // Everything seems ok, omit writing content to disk
        } else {
            // Responses that cannot provide their content, like BinaryFileResponse or
            // StreamedResponse, have no entity to store, so no entry is written
            if (false === $content = $response->getContent()) {
                return $key;
            }

            $digest = $this->generateContentDigest($response);
            $response->headers->set('X-Content-Digest', $digest);

            if (!$this->save($digest, $content, false)) {

View on GitHub (pinned to aa3a39d728)