guzzle/promises · error · LogicException
should never be serialized
Error message
should never be serialized
What it means
Classes using the internal NonSerializableTrait throw from __serialize() to block native PHP serialization. Promises and related objects hold live state (wait callbacks, closures, references to tasks/queues) that cannot survive serialize(), so the library fails fast instead of producing a broken, silently truncated object after unserialize(). static::class is interpolated into the message, naming the offending class.
Solutions
- Do not serialize promise objects; store only plain data (the awaited value or rejection reason) and rebuild the promise after retrieval.
- Call ->wait() to extract the concrete value before persisting, then wrap it in a new FulfilledPromise on the other side.
- Exclude promise objects from serialized payloads (unset them or use a whitelisting serializer).
- Log plain values, not promise instances.
Example fix
// before $_SESSION['result'] = $promise; // serialize() later throws LogicException // after $_SESSION['result'] = $promise->wait(); // plain value $promise = new \GuzzleHttp\Promise\FulfilledPromise($_SESSION['result']);
Defensive patterns
Strategy: try-catch
Validate before calling
if ($stateful instanceof \GuzzleHttp\Promise\PromiseInterface) {
throw new \LogicException('Refusing to serialize a promise; persist ->wait() value instead.');
} Type guard
function assertSerializable($value): void
{
if (is_object($value) && ($value instanceof \GuzzleHttp\Promise\PromiseInterface)) {
throw new \LogicException('Promises must not be serialized.');
}
} Try / catch
try {
$blob = serialize($obj);
} catch (\LogicException $e) {
$blob = serialize($obj->wait()); // persist the plain value instead
} Prevention
- Never store promises in $_SESSION, caches, or queue payloads; store awaited values.
- Sanitize payloads before serialize() to strip PromiseInterface instances.
- When logging context variables, unwrap or redact promises first.
When it happens
Trigger: Calling serialize($obj), or triggering indirect serialization via $_SESSION, var_export-less caches, APCu/Redis stores, igbinary, job queues, or error logs that serialize context, on an object of a class using NonSerializableTrait (promises, EachPromise, etc.).
Common situations: Storing pending promises in a PHP session between requests; caching promise objects in Redis/Memcached; pushing promises onto queue payloads; serializing exception traces that embed promises.
Related errors
- should never be unserialized
- Not enough promises to fulfill count
- You cannot create a FulfilledPromise with a promise.
- Cannot resolve a fulfilled promise
- Cannot reject a fulfilled promise
AI-assisted analysis of guzzle/promises@42118e66a5 (2026-09-14).
Data as JSON: /api/errors/04f793e4f08d1aeb.
Report an issue: GitHub.
Appendix: source
Thrown at src/NonSerializableTrait.php:14
<?php
declare(strict_types=1);
namespace GuzzleHttp\Promise;
/**
* @internal
*/
trait NonSerializableTrait
{
public function __serialize(): array
{
throw new \LogicException(static::class.' should never be serialized');
}
public function __unserialize(array $data): void
{
throw new \LogicException(static::class.' should never be unserialized');
}
}
View on GitHub (pinned to 42118e66a5)