composer/composer · error · TransportException

The "{url}" file could not be written to {destination}: {err

Error message

The "{url}" file could not be written to {destination}: {error}

What it means

TransportException thrown in CurlDownloader::initDownload() when fopen() of the response body destination fails. The destination is either the user-supplied $copyTo.'~' (when saving a file) or a php://temp stream; the captured PHP fopen error message is appended so the root cause (disk full, permissions, invalid path) is visible.

Source

Thrown at src/Composer/Util/Http/CurlDownloader.php:216

        if ($copyTo !== null) {
            $bodyTarget = $copyTo.'~';
        } else {
            $bodyTarget = 'php://temp/maxmemory:524288';
        }

        $errorMessage = '';
        set_error_handler(static function (int $code, string $msg) use (&$errorMessage): bool {
            if ($errorMessage) {
                $errorMessage .= "\n";
            }
            $errorMessage .= Preg::replace('{^fopen\(.*?\): }', '', $msg);

            return true;
        });
        $bodyHandle = fopen($bodyTarget, 'w+b');
        restore_error_handler();
        if (false === $bodyHandle) {
            throw new TransportException('The "'.Url::sanitize($url).'" file could not be written to '.($copyTo ?? 'a temporary file').': '.$errorMessage);
        }

        curl_setopt($curlHandle, CURLOPT_URL, $url);
        curl_setopt($curlHandle, CURLOPT_FOLLOWLOCATION, false);
        curl_setopt($curlHandle, CURLOPT_CONNECTTIMEOUT, 10);
        curl_setopt($curlHandle, CURLOPT_TIMEOUT, max((int) ini_get("default_socket_timeout"), 300));
        curl_setopt($curlHandle, CURLOPT_WRITEHEADER, $headerHandle);
        curl_setopt($curlHandle, CURLOPT_FILE, $bodyHandle);
        curl_setopt($curlHandle, CURLOPT_ENCODING, ""); // let cURL set the Accept-Encoding header to what it supports
        curl_setopt($curlHandle, CURLOPT_PROTOCOLS, CURLPROTO_HTTP | CURLPROTO_HTTPS);

        if ($attributes['ipResolve'] === 4) {
            curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V4);
        } elseif ($attributes['ipResolve'] === 6) {
            curl_setopt($curlHandle, CURLOPT_IPRESOLVE, CURL_IPRESOLVE_V6);
        }

        if ($attributes['retries'] > 0) {

View on GitHub (pinned to 6ffc117740)

Solutions

  1. Verify write permission and free space on the target directory and COMPOSER_HOME: `df -h` and `touch <dir>/test` then remove it.
  2. Set COMPOSER_HOME to a writable location: `export COMPOSER_HOME=/tmp/composer` (or a persistent writable dir).
  3. Fix ownership/permissions: `chown -R $USER <dir>` / `chmod -R u+w <dir>`.
  4. Clear the Composer cache (`composer clear-cache`) in case the cache dir is the culprit.

Example fix

// before: target dir not writable by current user
//   composer install   # => could not be written to /var/www/vendor/...~: Permission denied
// after
//   sudo chown -R $USER:$USER /var/www
//   composer install
Defensive patterns

Strategy: validation

Validate before calling

$dest = $copyTo ?? sys_get_temp_dir();
if (!is_writable(dirname($dest))) {
    throw new \RuntimeException('Destination dir not writable: '.dirname($dest));
}

Type guard

function isWritableTarget(?string $copyTo): bool {
    return $copyTo === null || is_writable(dirname($copyTo));
}

Try / catch

try {
    $downloader->get($url, [], $copyTo);
} catch (\Composer\Downloader\TransportException $e) {
    if (str_contains($e->getMessage(), 'could not be written to')) {
        // fix perms / free disk / set COMPOSER_HOME, then retry
    }
    throw $e;
}

Prevention

When it happens

Trigger: initDownload() reaches line 213 fopen($bodyTarget,'w+b') and it returns false; the set_error_handler captured $errorMessage. Happens when $copyTo points at a non-writable/unreachable directory, disk is full, or the temp stream cannot be allocated.

Common situations: COMPOSER_HOME or cache dir on a read-only filesystem; vendor dir owned by another user; disk/quota exhausted; very long path on Windows; SELinux denying writes; running Composer as a user without write permission to the target directory.

Related errors


AI-assisted analysis of composer/composer@6ffc117740 (2026-08-07). Data as JSON: /api/errors/4699252156e05723. Report an issue: GitHub.