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
- 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
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
- 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
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
- Could not write data to cache file %s.
- "%s" rule is deprecated, as turning a docblock type into a r
- "%s" rule is deprecated, as risky. The "??" and "?:" operato
- "%s" is deprecated as depends on context and personal prefer
- "%s" rule is deprecated, as it is a personal preference that
AI-assisted analysis of rectorphp/rector@408fcb0ff1 (2026-08-21).
Data as JSON: /api/errors/f7fe28bf8b2ab898.
Report an issue: GitHub.