Intervention/image · error · FileNotReadableException

Failed to open file from path "' . $path . '"

Error message

Failed to open file from path "' . $path . '"

What it means

File::fromPath() first validates the path via readableFilePathOrFail() (existence, readability) and then calls fopen(). If fopen() still returns false despite the passing pre-checks, this FileNotReadableException (a FilesystemException) is thrown. It signals a check-then-open race or an environment-level failure: the file was deleted or its permissions changed between the two calls, an open_basedir restriction interfered, or the process ran out of file descriptors.

Source

Thrown at src/File.php:56

    }

    /**
     * {@inheritdoc}
     *
     * @see FileInterface::fromPath()
     *
     * @throws InvalidArgumentException
     * @throws DirectoryNotFoundException
     * @throws FileNotFoundException
     * @throws FileNotReadableException
     * @throws StreamException
     */
    public static function fromPath(string $path): self
    {
        $stream = fopen(self::readableFilePathOrFail($path), 'r');

        if ($stream === false) {
            throw new FileNotReadableException('Failed to open file from path "' . $path . '"');
        }

        return new self($stream);
    }

    /**
     * {@inheritdoc}
     *
     * @see FileInterface::save()
     *
     * @throws InvalidArgumentException
     * @throws DirectoryNotFoundException
     * @throws FileNotWritableException
     * @throws StreamException
     */
    public function save(string $path): void
    {
        if ($path === '') {

View on GitHub (pinned to 5598b9e397)

Solutions

  1. Make temp-file lifecycles exclusive to one worker (unique tempnames via tempnam(), delete only after processing)
  2. Catch FilesystemException around read operations and retry once or re-enqueue the job
  3. Check open_basedir in php.ini and raise the open-file limit (ulimit -n) for long-running workers
  4. Verify the file still exists and is readable immediately before the call to shrink the race window

Example fix

// before
$file = File::fromPath($sharedTmpPath); // another worker unlinks it concurrently

// after
$path = tempnam(sys_get_temp_dir(), 'img_');
copy($sharedTmpPath, $path); // private copy
$file = File::fromPath($path);
Defensive patterns

Strategy: try-catch

Validate before calling

if (!is_string($path) || !is_file($path) || !is_readable($path)) {
    throw new RuntimeException('Image file missing or unreadable: ' . $path);
}
// note: a race can still occur between this check and the internal fopen()

Try / catch

use Intervention\Image\Exceptions\FilesystemException;

try {
    $file = File::fromPath($path);
} catch (FilesystemException $e) {
    // covers FileNotFoundException, FileNotReadableException and this race case
    Logger::warning('Failed to open image file', ['path' => $path]);
    throw new RuntimeException('Image source unavailable', 0, $e);
}

Prevention

When it happens

Trigger: Two processes handling the same temporary file where one unlinks it between Intervention's readability check and the fopen() call; permissions being revoked mid-request; a process at its open-file limit (ulimit -n); PHP running with an open_basedir boundary that excludes the real path.

Common situations: High-concurrency queue workers processing shared temp files that a cleanup job deletes; tmpwatch/systemd-tmpfiles removing files during long-running requests; shared hosting with restrictive open_basedir; fd exhaustion under heavy parallel image processing.

Related errors


AI-assisted analysis of Intervention/image@5598b9e397 (2026-08-23). Data as JSON: /api/errors/9db44e0ca0db2aae. Report an issue: GitHub.