guzzle/guzzle · error · GuzzleHttp\Exception\RequestException
Unable to add the cURL handle to the cURL multi handler: %s
Error message
Unable to add the cURL handle to the cURL multi handler: %s (%d).
What it means
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.
Source
Thrown at src/Handler/CurlMultiHandler.php:656
): void {
$this->isolateFromForeignActiveProxyTunnel($easy);
$multiHandle = $this->getMultiHandle();
// Unqualified curl_multi_add_handle so the test bootstrap shadow can
// override the result.
$result = curl_multi_add_handle($multiHandle, $easy->handle);
if (\CURLM_OK !== $result) {
if (\PHP_VERSION_ID < 80226 || (\PHP_VERSION_ID >= 80300 && \PHP_VERSION_ID < 80314)) {
// Before PHP 8.2.26 and 8.3.14, ext-curl kept the easy handle
// in its multi bookkeeping even when the native add failed
// (https://github.com/php/php-src/pull/16302); remove it so
// the handle can be disposed safely.
\curl_multi_remove_handle($multiHandle, $easy->handle);
}
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);
}
$this->markProxyTunnelActive($id, $easy);
if (isset($this->handles[$id])) {
$this->handles[$id]['attached'] = true;
}
}
private function isolateFromForeignActiveProxyTunnel(
#[\SensitiveParameter]
EasyHandle $easy
): void {
$signature = $easy->proxyTunnelSignature;
if ($signature === null || $this->activeProxyTunnelSignatures === []) {
return;
}View on GitHub (pinned to 9b200fc580)
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.
Example fix
// before: factory reuses an attached handle
$factory = new class implements CurlFactoryInterface {
private $h = curl_init();
public function create($req, $opts) { return new EasyHandle(/* handle: $this->h */); }
};
// after: factory returns a fresh handle each time
$factory = new class implements CurlFactoryInterface {
public function create($req, $opts) {
return new EasyHandle(/* handle: curl_init() */);
}
}; Defensive patterns
Strategy: try-catch
Try / catch
use GuzzleHttp\Exception\RequestException;
use GuzzleHttp\Promise\RejectionException;
try {
$promise = $client->requestAsync('GET', $url);
$response = $promise->wait();
} catch (RequestException $e) {
if (str_contains($e->getMessage(), 'Unable to add the cURL handle to the cURL multi handler')) {
// Inspect the libcurl code in the message; rebuild handler and retry.
$client = rebuildClientWithFreshFactory();
return $client->requestAsync('GET', $url)->wait();
}
throw $e;
} Prevention
- 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.
When it happens
Trigger: 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()`.
Common situations: 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).
Related errors
- Can not initialize cURL handle.
- Unable to create persistent cURL share handle:
- Invalid crypto_method request option: TLS 1.3 not supported
- Unable to set cURL option CURLOPT_FRESH_CONNECT.
- Unable to apply the %s cURL option required to isolate the t
AI-assisted analysis of guzzle/guzzle@9b200fc580 (2026-08-04).
Data as JSON: /data/errors/108bfcb7f39d766b.json.
Report an issue: GitHub.