{"id":"108bfcb7f39d766b","repo":"guzzle/guzzle","slug":"unable-to-add-the-curl-handle-to-the-curl-multi-ha","errorCode":null,"errorMessage":"Unable to add the cURL handle to the cURL multi handler: %s (%d).","messagePattern":"Unable to add the cURL handle to the cURL multi handler: (.+?) \\((.+?)\\)\\.","errorType":"exception","errorClass":"GuzzleHttp\\Exception\\RequestException","httpStatus":null,"severity":"error","filePath":"src/Handler/CurlMultiHandler.php","lineNumber":656,"sourceCode":"    ): void {\n        $this->isolateFromForeignActiveProxyTunnel($easy);\n\n        $multiHandle = $this->getMultiHandle();\n\n        // Unqualified curl_multi_add_handle so the test bootstrap shadow can\n        // override the result.\n        $result = curl_multi_add_handle($multiHandle, $easy->handle);\n\n        if (\\CURLM_OK !== $result) {\n            if (\\PHP_VERSION_ID < 80226 || (\\PHP_VERSION_ID >= 80300 && \\PHP_VERSION_ID < 80314)) {\n                // Before PHP 8.2.26 and 8.3.14, ext-curl kept the easy handle\n                // in its multi bookkeeping even when the native add failed\n                // (https://github.com/php/php-src/pull/16302); remove it so\n                // the handle can be disposed safely.\n                \\curl_multi_remove_handle($multiHandle, $easy->handle);\n            }\n\n            throw new RequestException(\\sprintf('Unable to add the cURL handle to the cURL multi handler: %s (%d).', (string) \\curl_multi_strerror($result), $result), $easy->request);\n        }\n\n        $this->markProxyTunnelActive($id, $easy);\n\n        if (isset($this->handles[$id])) {\n            $this->handles[$id]['attached'] = true;\n        }\n    }\n\n    private function isolateFromForeignActiveProxyTunnel(\n        #[\\SensitiveParameter]\n        EasyHandle $easy\n    ): void {\n        $signature = $easy->proxyTunnelSignature;\n\n        if ($signature === null || $this->activeProxyTunnelSignatures === []) {\n            return;\n        }","sourceCodeStart":638,"sourceCodeEnd":674,"githubUrl":"https://github.com/guzzle/guzzle/blob/9b200fc5805036b331d6031199880dadecae0275/src/Handler/CurlMultiHandler.php#L638-L674","documentation":"Thrown at request time when the native `curl_multi_add_handle()` call returns anything other than CURLM_OK while registering an easy handle on the multi handle. The message includes the libcurl error string and code so the underlying cause (e.g. CURLM_BAD_HANDLE, CURLM_OUT_OF_MEMORY) is visible. It wraps an irrecoverable transport-layer registration failure into a Guzzle RequestException tied to the failing request.","triggerScenarios":"Issuing a request through CurlMultiHandler when libcurl refuses to attach the easy handle: an already-attached handle, an out-of-memory condition, or a corrupted/invalid easy handle from a custom handle_factory. The throw is at src/Handler/CurlMultiHandler.php:656 inside `addHandleToMulti()`, reached via the handler's `__invoke()`.","commonSituations":"A custom handle_factory returning a handle that is already attached to another (or the same) multi handle; resource exhaustion under heavy concurrency; buggy test shims overriding curl_multi_add_handle. On PHP < 8.2.26 / 8.3.14 the stale bookkeeping is cleaned up before throwing (lines 648-654).","solutions":["Inspect the reported libcurl code: CURLM_BAD_HANDLE points to a reused/invalid handle from a custom handle_factory — fix the factory to return fresh handles.","CURLM_OUT_OF_MEMORY indicates system-level exhaustion; reduce concurrency or free resources and retry.","If using a custom handle_factory, ensure each create() returns a brand-new curl handle not attached to any multi handle.","Remove any test bootstrap that shadows curl_multi_add_handle unless it is deliberately simulating failure."],"exampleFix":"// before: factory reuses an attached handle\n$factory = new class implements CurlFactoryInterface {\n    private $h = curl_init();\n    public function create($req, $opts) { return new EasyHandle(/* handle: $this->h */); }\n};\n// after: factory returns a fresh handle each time\n$factory = new class implements CurlFactoryInterface {\n    public function create($req, $opts) {\n        return new EasyHandle(/* handle: curl_init() */);\n    }\n};","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"use GuzzleHttp\\Exception\\RequestException;\nuse GuzzleHttp\\Promise\\RejectionException;\n\ntry {\n    $promise = $client->requestAsync('GET', $url);\n    $response = $promise->wait();\n} catch (RequestException $e) {\n    if (str_contains($e->getMessage(), 'Unable to add the cURL handle to the cURL multi handler')) {\n        // Inspect the libcurl code in the message; rebuild handler and retry.\n        $client = rebuildClientWithFreshFactory();\n        return $client->requestAsync('GET', $url)->wait();\n    }\n    throw $e;\n}","preventionTips":["A custom handle_factory must return a fresh, unattached curl handle on every create() call.","Avoid reusing easy handles across requests or handlers.","On persistent CURLM_OUT_OF_MEMORY, reduce concurrency or restart the process."],"tags":["curl","runtime","transfer","resource-exhaustion"],"analyzedSha":"9b200fc5805036b331d6031199880dadecae0275","analyzedAt":"2026-08-04T21:24:26.648Z","schemaVersion":2}