guzzle/guzzle · error · RuntimeException

Unable to save file

Error message

Unable to save file %s

What it means

save() calls file_put_contents($filename, $jsonStr, LOCK_EX) and treats a false return as a hard failure. The path is escaped with DiagnosticValue::escape so control bytes in the path cannot tamper with the exception message. Any IO-level write failure (missing directory, unwritable target, disk full, lock contention) lands here.

Solutions

  1. Ensure the parent directory exists and is writable: mkdir(dirname($path), 0700, true) before constructing the jar.
  2. Verify the runtime user owns or can write the target file.
  3. Check disk space and inode usage on the target filesystem.
  4. Use an absolute path; confirm the path is not on a read-only mount.

Example fix

// before
new FileCookieJar('/var/run/app/cookies.json');

// after
$path = '/var/run/app/cookies.json';
@mkdir(dirname($path), 0700, true);
new FileCookieJar($path);
Defensive patterns

Strategy: validation

Validate before calling

// Verify the directory is writable before constructing the jar.
function ensureWritableFile(string $path): void {
    $dir = \dirname($path);
    if (!\is_dir($dir)) {
        \mkdir($dir, 0700, true);
    }
    if (!\is_writable($dir)) {
        throw new \RuntimeException("Cookie dir not writable: {$dir}");
    }
}

Try / catch

try {
    $jar = new FileCookieJar($path);
    $jar->save($path);
} catch (\RuntimeException $e) {
    // IO failure; degrade gracefully (e.g. fall back to in-memory jar)
}

Prevention

When it happens

Trigger: Constructor path points at a directory that does not exist or is not writable by the PHP runtime user; the file is on a read-only filesystem; LOCK_EX could not be acquired; out of disk or inodes.

Common situations: Containerized deployments where the cookie path is on an unmounted volume; per-user directories that were never created; permissions reset by a deployment; PHP-FPM running as www-data while the target dir is owned by root.

Related errors


AI-assisted analysis of guzzle/guzzle@d1cbca7697 (2026-08-06). Data as JSON: /api/errors/1a2ea1249921e5f2. Report an issue: GitHub.

Appendix: source

Thrown at src/Cookie/FileCookieJar.php:110

    {
        $json = [];
        /** @var SetCookie $cookie */
        foreach ($this as $cookie) {
            if (CookieJar::shouldPersist($cookie, $this->storeSessionCookies)) {
                $data = $cookie->toArray();
                $data['HostOnly'] = $cookie->getHostOnly();
                $json[] = $data;
            }
        }

        try {
            $jsonStr = \json_encode($json, \JSON_HEX_TAG | \JSON_THROW_ON_ERROR);
        } catch (\JsonException $e) {
            throw new \RuntimeException('Unable to encode cookie data', 0, $e);
        }

        if (false === \file_put_contents($filename, $jsonStr, \LOCK_EX)) {
            throw new \RuntimeException(\sprintf('Unable to save file %s', DiagnosticValue::escape($filename)));
        }

        // Best-effort: restrict the cookie file to the owner so persisted
        // cookies are not world-readable.
        @\chmod($filename, 0600);
    }

    /**
     * Load cookies from a JSON formatted file.
     *
     * Old cookies are kept unless overwritten by newly loaded ones.
     * Cookie records are constructed before any are passed to setCookie().
     *
     * @param string $filename Cookie file to load.
     *
     * @throws \RuntimeException if the file cannot be loaded or is invalid
     */
    public function load(string $filename): void

View on GitHub (pinned to d1cbca7697)