composer/composer · error · \RuntimeException
Failed getting info from curl handle
Error message
Failed getting info from curl handle {$i} ({url}) What it means
During CurlDownloader::tick(), after curl_multi_info_read reports a finished job, `curl_getinfo($curlHandle)` returns false, meaning the handle is no longer queryable. Composer throws because it cannot recover the HTTP status/timing info needed to build the response.
Solutions
- Upgrade the PHP curl extension and libcurl (`apt-get install --only-upgrade php-curl libcurl4`).
- Run `composer self-update --2` to get the latest downloader fixes.
- If reproducible, rule out an HTTP/2 stream bug by forcing HTTP/1.1 (avoid HTTP2-capable libcurl or set COMPOSER_IPRESOLVE).
- Report upstream with the output of `php -i | grep -i cURL` if it persists.
Example fix
// before RUN apt-get install -y php8.1-curl=8.1.0 # old libcurl // after RUN apt-get install -y --only-upgrade php8.1-curl libcurl4
Defensive patterns
Strategy: retry
Validate before calling
// Sanity-check curl health before bulk downloads
$h = curl_init('http://127.0.0.1');
$info = $h !== false ? @curl_getinfo($h) : false;
if ($info === false) {
throw new \RuntimeException('curl extension is broken; upgrade libcurl/php-curl.');
} Try / catch
try {
$downloader->tick();
} catch (\RuntimeException $e) {
if (str_starts_with($e->getMessage(), 'Failed getting info from curl handle')) {
// discard this downloader, build a fresh CurlDownloader, and retry the jobs
}
throw $e;
} Prevention
- Keep libcurl and php-curl at current stable versions.
- Avoid mixing PHP builds with a mismatched libcurl ABI.
- Use `composer self-update` to get the latest downloader fixes.
When it happens
Trigger: CurlDownloader::tick() processes a completed handle (src/Composer/Util/Http/CurlDownloader.php:359-361); if the underlying handle was closed/invalidated between dispatch and info-read (rare internal state issue, libcurl/PHP-curl ABI mismatch, or a curl extension bug), curl_getinfo returns false.
Common situations: Bugs in libcurl or the PHP curl extension on certain versions; handle removed concurrently; very rare — usually signals a corrupt curl state, often after a curl extension crash or build mismatch.
Related errors
- Unexpected cURL error
- cURL error: ( ): cURL message
- A fallback driver should be setup if getRepoData returns…
- Can't run git rev-list. You must ensure to run compile from…
- Composer 2.3.0 dropped support for autoloading on PHP <5.6…
AI-assisted analysis of composer/composer@c435d285c9 (2026-08-07).
Data as JSON: /api/errors/35bd24fb04758d28.
Report an issue: GitHub.
Appendix: source
Thrown at src/Composer/Util/Http/CurlDownloader.php:361
$active = true;
$this->checkCurlResult(curl_multi_exec($this->multiHandle, $active));
if (-1 === curl_multi_select($this->multiHandle, $this->selectTimeout)) {
// sleep in case select returns -1 as it can happen on old php versions or some platforms where curl does not manage to do the select
usleep(150);
}
while ($progress = curl_multi_info_read($this->multiHandle)) {
$curlHandle = $progress['handle'];
$result = $progress['result'];
$i = (int) $curlHandle;
if (!isset($this->jobs[$i])) {
continue;
}
$progress = curl_getinfo($curlHandle);
if (false === $progress) {
throw new \RuntimeException('Failed getting info from curl handle '.$i.' ('.Url::sanitize($this->jobs[$i]['url']).')');
}
$job = $this->jobs[$i];
unset($this->jobs[$i]);
$error = curl_error($curlHandle);
$errno = curl_errno($curlHandle);
curl_multi_remove_handle($this->multiHandle, $curlHandle);
if (\PHP_VERSION_ID < 80000) {
curl_close($curlHandle);
}
$headers = null;
$statusCode = null;
$response = null;
try {
// TODO progress
if (CURLE_OK !== $errno || $error || $result !== CURLE_OK) {
$errno = $errno ?: $result;
if (!$error && function_exists('curl_strerror')) {View on GitHub (pinned to c435d285c9)