{"record":{"id":"f7fe28bf8b2ab898","repo":"rectorphp/rector","slug":"file-s-is-not-readable","errorCode":null,"errorMessage":"File %s is not readable","messagePattern":"File (.+?) is not readable","errorType":"exception","errorClass":"ShouldNotHappenException","httpStatus":null,"severity":"error","filePath":"src/Util/FileHasher.php","lineNumber":31,"sourceCode":"     * cryptographic insecure hashing of a string\n     */\n    public function hash(string $string): string\n    {\n        return hash($this->getAlgo(), $string);\n    }\n    /**\n     * cryptographic insecure hashing of files\n     *\n     * @param string[] $files\n     */\n    public function hashFiles(array $files): string\n    {\n        $configHash = '';\n        $algo = $this->getAlgo();\n        foreach ($files as $file) {\n            $hash = hash_file($algo, $file);\n            if ($hash === \\false) {\n                throw new ShouldNotHappenException(sprintf('File %s is not readable', $file));\n            }\n            $configHash .= $hash;\n        }\n        return $configHash;\n    }\n    private function getAlgo(): string\n    {\n        // see https://php.watch/articles/php-hash-benchmark\n        if (\\PHP_VERSION_ID >= 80100) {\n            // if xxh128 is available use it, as it is way faster\n            return 'xxh128';\n        }\n        return 'md4';\n    }\n}\n","sourceCodeStart":13,"sourceCodeEnd":47,"githubUrl":"https://github.com/rectorphp/rector/blob/408fcb0ff1833e3d26cbc9b04c23a77565814a16/src/Util/FileHasher.php#L13-L47","documentation":"FileHasher::hashFiles() concatenates hash_file($algo, $file) for each path (xxh128 on PHP 8.1+, murmur3-ish fallback below) to build a deterministic aggregate hash. hash_file() returns false when the path is missing, unreadable, or not a regular file, and any false aborts with ShouldNotHappenException('File %s is not readable').","triggerScenarios":"The file list passed to hashFiles() contains a deleted file (raced between discovery and hashing), a directory, an unreadable-permission file, or a symlink pointing at a removed target; PHP open_basedir restrictions can also force hash_file() to false.","commonSituations":"Editor/another process rewriting files during a long Rector run; container/VM volume permission mismatches; open_basedir excluding the analyzed path; temp analysis files cleaned up mid-run.","solutions":["Filter the list before hashing: keep only is_file() && is_readable() entries","Fix filesystem permissions (or run the user owning the files) so PHP can read every analyzed path","Check php -i for open_basedir and add/expand the analyzed directories","Re-run when no concurrent writer (formatter watcher, sync client) is mutating the tree"],"exampleFix":"// before\n$hash = $this->fileHasher->hashFiles($files);\n\n// after\n$files = array_values(array_filter(\n    $files,\n    static fn (string $f): bool => is_file($f) && is_readable($f)\n));\n$hash = $this->fileHasher->hashFiles($files);","handlingStrategy":"validation","validationCode":"// pre-filter anything about to be hashed\n$readableFiles = array_values(array_filter(\n    $files,\n    static fn (string $file): bool => is_file($file) && is_readable($file)\n));\n\nif (count($readableFiles) !== count($files)) {\n    trigger_error('Skipping unreadable files during hashing', E_USER_WARNING);\n}\n\n$hash = $fileHasher->hashFiles($readableFiles);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Snapshot the file list and stat each file immediately before hashing in long-running pipelines","Give the runtime user read access to the whole analyzed tree (containers/VM mounts especially)","Verify open_basedir covers the project when running in hardened PHP setups"],"tags":["php","file-io","permissions","hashing","rector"],"backgroundTag":"file-not-readable","analyzedSha":"408fcb0ff1833e3d26cbc9b04c23a77565814a16","analyzedAt":"2026-08-21T05:11:02.643Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}