{"record":{"id":"fe49aa0fbb3ff352","repo":"sebastianbergmann/phpunit","slug":"directory-s-does-not-exist-and-could-not-be-cre-fe49aa","errorCode":null,"errorMessage":"Directory \"%s\" does not exist and could not be created","messagePattern":"Directory \"(.+?)\" does not exist and could not be created","errorType":"exception","errorClass":"DirectoryDoesNotExistException","httpStatus":null,"severity":"error","filePath":"src/Runner/TestRunHistory/DefaultTestRunHistory.php","lineNumber":182,"sourceCode":"    /**\n     * Persists only the entries that were touched since load(). This drops\n     * the entries of tests that no longer exist and must only be used when\n     * the current test run executed every test that exists.\n     *\n     * @throws Exception\n     */\n    public function persistAndPrune(): void\n    {\n        $this->writeToFile(true);\n    }\n\n    /**\n     * @throws Exception\n     */\n    private function writeToFile(bool $prune): void\n    {\n        if (!Filesystem::createDirectory(dirname($this->filename))) {\n            throw new DirectoryDoesNotExistException(dirname($this->filename));\n        }\n\n        $handle = fopen($this->filename, 'c+');\n\n        if ($handle === false) {\n            // @codeCoverageIgnoreStart\n            return;\n            // @codeCoverageIgnoreEnd\n        }\n\n        flock($handle, LOCK_EX);\n\n        if ($prune) {\n            $defects = [];\n\n            foreach ($this->defects as $id => $status) {\n                if (isset($this->changedDefects[$id])) {\n                    $defects[$id] = $status;","sourceCodeStart":164,"sourceCodeEnd":200,"githubUrl":"https://github.com/sebastianbergmann/phpunit/blob/f123cdb2a2d49f15025794166cfed8bda8627dd2/src/Runner/TestRunHistory/DefaultTestRunHistory.php#L164-L200","documentation":"DefaultTestRunHistory::writeToFile() (reached from persist()/persistAndPrune()) stores the test run history file and, like the test index, first tries Filesystem::createDirectory() on dirname($this->filename); when creation fails it throws DirectoryDoesNotExistException('Directory \"%s\" does not exist and could not be created'). The parent directory of the run-history file neither exists nor is creatable by the PHP process.","triggerScenarios":"Persisting the run history when its target directory is missing and cannot be created — read-only filesystem, missing write permission, or a path excluded by open_basedir.","commonSituations":"CI containers with read-only project mounts; a run-history path pointing outside the sandbox; permission changes between runs (running as different users).","solutions":["Create the directory beforehand and make it writable by the PHP process: mkdir -p path/to/dir.","Configure the run history/cache location to a writable directory.","Verify open_basedir settings and that the volume is mounted read-write in containers."],"exampleFix":"# before\nphpunit   # writeToFile() fails: Directory \"/var/cache/phpunit\" does not exist and could not be created\n\n# after\nmkdir -p /var/cache/phpunit\nphpunit","handlingStrategy":"validation","validationCode":"// before persisting the run history, ensure its directory is writable\n$dir = dirname($historyFilename);\nif (!is_dir($dir) && !@mkdir($dir, 0777, true) && !is_dir($dir)) {\n    throw new RuntimeException(\"Cannot create run-history directory {$dir}\");\n}\nif (!is_writable($dir)) {\n    throw new RuntimeException(\"Run-history directory {$dir} is not writable\");\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Point the run-history/cache location at a writable volume in containers.","Check open_basedir when hardening php.ini so the history path stays reachable.","Verify with is_writable() during environment smoke tests in CI."],"tags":["phpunit","run-history","filesystem","permissions","ci"],"backgroundTag":"directory-creation-failed","analyzedSha":"f123cdb2a2d49f15025794166cfed8bda8627dd2","analyzedAt":"2026-08-23T01:20:58.058Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}