{"record":{"id":"ad63f065d21046ca","repo":"symfony/symfony","slug":"cache-directory-is-not-writable-s-ad63f0","errorCode":null,"errorMessage":"Cache directory is not writable (%s).","messagePattern":"Cache directory is not writable \\((.+?)\\)\\.","errorType":"exception","errorClass":"Symfony\\Component\\Cache\\Exception\\CacheException","httpStatus":null,"severity":"error","filePath":"src/Symfony/Component/Cache/Traits/FilesystemTrait.php","lineNumber":95,"sourceCode":"    {\n        $file = $this->getFile($id);\n\n        return is_file($file) && (@filemtime($file) > time() || $this->doFetch([$id]));\n    }\n\n    protected function doSave(array $values, int $lifetime): array|bool\n    {\n        $expiresAt = $lifetime ? (time() + $lifetime) : 0;\n        $values = $this->marshaller->marshall($values, $failed);\n\n        foreach ($values as $id => $value) {\n            if (!$this->write($this->getFile($id, true), $expiresAt.\"\\n\".rawurlencode($id).\"\\n\".$value, $expiresAt)) {\n                $failed[] = $id;\n            }\n        }\n\n        if ($failed && !is_writable($this->directory)) {\n            throw new CacheException(\\sprintf('Cache directory is not writable (%s).', $this->directory));\n        }\n\n        return $failed;\n    }\n\n    private function getFileKey(string $file): string\n    {\n        if (!$h = @fopen($file, 'r')) {\n            return '';\n        }\n\n        fgets($h); // expiry\n        $encodedKey = fgets($h);\n        fclose($h);\n\n        return rawurldecode(rtrim($encodedKey));\n    }\n}","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/symfony/symfony/blob/698e28026c22cf35d032cdb6e800db48b1535790/src/Symfony/Component/Cache/Traits/FilesystemTrait.php#L77-L113","documentation":"FilesystemTrait::doSave() writes each item via write(); if some writes failed and the cache directory itself is not writable, the adapter cannot recover and throws CacheException. The check is scoped to the directory so a transient single-file failure stays a soft failure (returned in $failed) while a permission problem surfaces loudly.","triggerScenarios":"Cache directory owned by a different user than the running process; read-only mounted volume; SELinux denying writes; Docker volume with root-owned files and a non-root container user.","commonSituations":"CLI (root) warming cache then web server (www-data) failing to write; Kubernetes emptyDir with wrong fsGroup; deploy that created the dir with umask 0022.","solutions":["chown the directory to the web/worker user and chmod 0o775 (or 0o777 for shared): chown -R www-data:www-data var/cache.","Ensure the same user (or a shared group) runs cache-warming commands and the runtime.","Point cache to a location the process owns, or to sys_get_temp_dir() (omit $directory)."],"exampleFix":"# before: dir owned by root, app runs as www-data\n# ls -ld var/cache  ->  drwxr-xr-x root root\n\n# after\nsudo chown -R www-data:www-data var/cache\nsudo chmod -R 0o775 var/cache","handlingStrategy":"validation","validationCode":"if (!is_writable($dir)) {\n    throw new \\RuntimeException(sprintf('Cache dir %s is not writable by user %s', $dir, get_current_user()));\n}\nnew FilesystemAdapter($ns, 0, $dir);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Run cache warmup and the runtime under the same user/group.","chmod the cache dir to 0o775 and set the shared group.","In containers, set the correct fsGroup/UID for the volume."],"tags":["filesystem","permissions","cache"],"analyzedSha":"698e28026c22cf35d032cdb6e800db48b1535790","analyzedAt":"2026-08-06T23:40:49.025Z","schemaVersion":2},"datasetVersion":"2026-08-07T03:17:09.362Z"}