{"record":{"id":"1461402a359301b0","repo":"composer/composer","slug":"failed-getting-info-from-curl-handle-i-url","errorCode":null,"errorMessage":"Failed getting info from curl handle {i} ({url})","messagePattern":"Failed getting info from curl handle (.+?) \\((.+?)\\)","errorType":"exception","errorClass":"RuntimeException","httpStatus":null,"severity":"error","filePath":"src/Composer/Util/Http/CurlDownloader.php","lineNumber":361,"sourceCode":"\n        $active = true;\n        $this->checkCurlResult(curl_multi_exec($this->multiHandle, $active));\n        if (-1 === curl_multi_select($this->multiHandle, $this->selectTimeout)) {\n            // 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\n            usleep(150);\n        }\n\n        while ($progress = curl_multi_info_read($this->multiHandle)) {\n            $curlHandle = $progress['handle'];\n            $result = $progress['result'];\n            $i = (int) $curlHandle;\n            if (!isset($this->jobs[$i])) {\n                continue;\n            }\n\n            $progress = curl_getinfo($curlHandle);\n            if (false === $progress) {\n                throw new \\RuntimeException('Failed getting info from curl handle '.$i.' ('.Url::sanitize($this->jobs[$i]['url']).')');\n            }\n            $job = $this->jobs[$i];\n            unset($this->jobs[$i]);\n            $error = curl_error($curlHandle);\n            $errno = curl_errno($curlHandle);\n            curl_multi_remove_handle($this->multiHandle, $curlHandle);\n            if (\\PHP_VERSION_ID < 80000) {\n                curl_close($curlHandle);\n            }\n\n            $headers = null;\n            $statusCode = null;\n            $response = null;\n            try {\n                // TODO progress\n                if (CURLE_OK !== $errno || $error || $result !== CURLE_OK) {\n                    $errno = $errno ?: $result;\n                    if (!$error && function_exists('curl_strerror')) {","sourceCodeStart":343,"sourceCodeEnd":379,"githubUrl":"https://github.com/composer/composer/blob/6ffc1177404d0c50119c22dde6564a380f4a82c9/src/Composer/Util/Http/CurlDownloader.php#L343-L379","documentation":"Thrown when curl_getinfo() returns false for a curl easy-handle inside the multi-exec processing loop of CurlDownloader. Composer relies on curl_getinfo to read transfer metadata (HTTP code, size, timing) before rejecting/completing a job; a false return means the underlying libcurl handle is no longer queryable, so Composer cannot continue safely. The message includes the internal handle id ($i) and the sanitized URL so you can identify which request failed. This is a low-level libcurl/PHP interop failure rather than a normal HTTP error.","triggerScenarios":"Occurs in CurlDownloader::select()/loop when curl_multi_info_read yields a finished handle but curl_getinfo on that handle returns false — typically when the handle was already closed, the multi-handle is in a bad state, or libcurl hit an internal error mid-transfer.","commonSituations":"Hitting this during a `composer install/update` behind a flaky proxy, after a process fork that duplicated curl handles, on PHP versions with libcurl bugs, or when a custom transport/curl option conflicts with Composer's multi-handle. Rare in normal use; usually signals a corrupted curl session or environment (e.g. outdated php-curl extension).","solutions":["Update PHP and the php-curl/libcurl extension to the latest patch release for your PHP version.","Check COMPOSER_CAFILE / openssl CA bundle and proxy env vars (HTTP_PROXY/HTTPS_PROXY) for corruption that could destabilize transfers.","Reproduce with `composer <command> -vvv` to capture the exact URL/handle, then disable parallel downloads via COMPOSER_MAX_PARALLEL_HTTP=1 to rule out multi-handle races.","If it persists on a specific URL, test that URL directly with curl on the host to isolate a libcurl-level fault."],"exampleFix":"// before (env instability)\nCOMPOSER_MAX_PARALLEL_HTTP=50 composer install\n// after (isolate multi-handle races, upgrade path)\nCOMPOSER_MAX_PARALLEL_HTTP=1 composer install -vvv","handlingStrategy":"retry","validationCode":"// Cannot fully prevent (libcurl-internal); validate transport health before the run.\nif (!extension_loaded('curl')) { throw new \\RuntimeException('php-curl extension required'); }\nif (!is_callable('curl_multi_init')) { throw new \\RuntimeException('curl multi support missing'); }","typeGuard":null,"tryCatchPattern":"try { $response = $httpDownloader->get($url); } catch (\\RuntimeException $e) {\n    if (str_starts_with($e->getMessage(), 'Failed getting info from curl handle')) {\n        // transient libcurl state — retry once after reset, then surface\n        $httpDownloader->reset(); $response = $httpDownloader->get($url);\n    } else { throw $e; }\n}","preventionTips":["Keep php-curl/libcurl patched to current stable.","Set COMPOSER_MAX_PARALLEL_HTTP=1 when debugging multi-handle issues.","Avoid forking the PHP process while Composer's curl handles are live."],"tags":["network","curl","php","composer","http"],"analyzedSha":"6ffc1177404d0c50119c22dde6564a380f4a82c9","analyzedAt":"2026-08-07T00:01:08.491Z","schemaVersion":2},"datasetVersion":"2026-08-07T02:17:10.218Z"}