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

  1. 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.
  2. CURLM_OUT_OF_MEMORY indicates system-level exhaustion; reduce concurrency or free resources and retry.
  3. If using a custom handle_factory, ensure each create() returns a brand-new curl handle not attached to any multi handle.
  4. 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

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


AI-assisted analysis of guzzle/guzzle@9b200fc580 (2026-08-04). Data as JSON: /data/errors/108bfcb7f39d766b.json. Report an issue: GitHub.