rectorphp/rector · error · ShouldNotHappenException

File %s is not readable

Error message

File %s is not readable

What it means

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').

Source

Thrown at src/Util/FileHasher.php:31

     * cryptographic insecure hashing of a string
     */
    public function hash(string $string): string
    {
        return hash($this->getAlgo(), $string);
    }
    /**
     * cryptographic insecure hashing of files
     *
     * @param string[] $files
     */
    public function hashFiles(array $files): string
    {
        $configHash = '';
        $algo = $this->getAlgo();
        foreach ($files as $file) {
            $hash = hash_file($algo, $file);
            if ($hash === \false) {
                throw new ShouldNotHappenException(sprintf('File %s is not readable', $file));
            }
            $configHash .= $hash;
        }
        return $configHash;
    }
    private function getAlgo(): string
    {
        // see https://php.watch/articles/php-hash-benchmark
        if (\PHP_VERSION_ID >= 80100) {
            // if xxh128 is available use it, as it is way faster
            return 'xxh128';
        }
        return 'md4';
    }
}

View on GitHub (pinned to 408fcb0ff1)

Solutions

  1. Filter the list before hashing: keep only is_file() && is_readable() entries
  2. Fix filesystem permissions (or run the user owning the files) so PHP can read every analyzed path
  3. Check php -i for open_basedir and add/expand the analyzed directories
  4. Re-run when no concurrent writer (formatter watcher, sync client) is mutating the tree

Example fix

// before
$hash = $this->fileHasher->hashFiles($files);

// after
$files = array_values(array_filter(
    $files,
    static fn (string $f): bool => is_file($f) && is_readable($f)
));
$hash = $this->fileHasher->hashFiles($files);
Defensive patterns

Strategy: validation

Validate before calling

// pre-filter anything about to be hashed
$readableFiles = array_values(array_filter(
    $files,
    static fn (string $file): bool => is_file($file) && is_readable($file)
));

if (count($readableFiles) !== count($files)) {
    trigger_error('Skipping unreadable files during hashing', E_USER_WARNING);
}

$hash = $fileHasher->hashFiles($readableFiles);

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of rectorphp/rector@408fcb0ff1 (2026-08-21). Data as JSON: /api/errors/f7fe28bf8b2ab898. Report an issue: GitHub.