{"record":{"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/d1cbca76970939a9c2ced55b1e25ea26f34fc773/src/Handler/CurlMultiHandler.php#L638-L674","documentation":"Thrown from addHandleToMulti() when curl_multi_add_handle() returns a code other than CURLM_OK while attaching an easy handle to the multi handle during an async transfer. Guzzle wraps it as a RequestException so the promise rejects instead of running the transfer in an undefined state. The message embeds curl_multi_strerror() text and the numeric libcurl result code.","triggerScenarios":"Awaiting any async request on a CurlMultiHandler (e.g., $client->requestAsync(...)->wait(), or $handler->addRequest(...)) where the native curl_multi_add_handle call fails. Possible libcurl results include CURLM_OUT_OF_MEMORY, CURLM_BAD_EASY_HANDLE, CURLM_ADDED_ALREADY, and CURLM_INTERNAL_ERROR.","commonSituations":"Memory exhaustion under very high concurrency; an easy handle accidentally added twice (custom handle_factory returning a pooled/already-attached handle); a handle reused after it was closed; a buggy or very old libcurl returning CURLM_INTERNAL_ERROR.","solutions":["Inspect the numeric code and strerror in the message: CURLM_OUT_OF_MEMORY is transient, CURLM_ADDED_ALREADY points to a handle reused before removal.","If using a custom handle_factory, ensure it returns a fresh, never-attached easy handle on every call.","Bound concurrency with max_host_connections/max_total_connections and free handles promptly to avoid memory pressure.","Upgrade libcurl/PHP ext-curl if CURLM_INTERNAL_ERROR recurs on stock handles."],"exampleFix":"// before\n$promise = $handler->addRequest($easy);\n$promise->wait(); // RequestException: Unable to add the cURL handle ... (CURLM_OUT_OF_MEMORY)\n\n// after - bounded caps + retry on transient multi errors\nuse GuzzleHttp\\Exception\\RequestException;\ntry {\n    $promise->wait();\n} catch (RequestException $e) {\n    if (str_contains($e->getMessage(), 'OUT_OF_MEMORY')) {\n        // shed load, free handles, then retry once\n    }\n    throw $e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"use GuzzleHttp\\Exception\\RequestException;\n\ntry {\n    $promise->wait();\n} catch (RequestException $e) {\n    // $e->getMessage() ends with \"... (CURLM_<CODE>).\"\n    if (str_contains($e->getMessage(), 'OUT_OF_MEMORY')) {\n        // transient: shed load, free handles, retry once\n    }\n    throw $e;\n}","preventionTips":["Bound concurrency with max_host_connections / max_total_connections","Return fresh, never-attached handles from any custom handle_factory","Never reuse an easy handle after close or removal","Upgrade libcurl/ext-curl for stability fixes"],"tags":["curl","async","libcurl","memory"],"backgroundTag":null,"analyzedSha":"d1cbca76970939a9c2ced55b1e25ea26f34fc773","analyzedAt":"2026-08-06T00:37:27.795Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}