guzzle/promises · error · LogicException
should never be unserialized
Error message
should never be unserialized
What it means
This is the unserialize-side twin of the serialization guard: __unserialize() on any class using NonSerializableTrait throws immediately, so an object of that class can never be restored from a serialized payload. This exists so that even a payload crafted directly (bypassing __serialize) cannot resurrect a promise with dead callbacks and broken internal state.
Solutions
- Purge/regenerate caches and sessions that contain serialized promise objects.
- Change persisted format to plain data; reconstruct promises explicitly after unserialize().
- Use unserialize() allowed_classes options (or similar) to keep promise classes out of untrusted payloads.
- Audit serialization call sites for promise-typed values.
Example fix
// before $promise = unserialize($cached); // LogicException // after $value = unserialize($cached); // plain value $promise = new \GuzzleHttp\Promise\FulfilledPromise($value);
Defensive patterns
Strategy: try-catch
Validate before calling
// Detect non-restorable payloads before unserialize():
if (preg_match('/O:\d+:"[^"]*(Promise|EachPromise)[^"]*"/', $blob) === 1) {
throw new \LogicException('Payload contains promise objects; discard or migrate it.');
} Try / catch
try {
$value = unserialize($blob, ['allowed_classes' => false]);
} catch (\LogicException $e) {
$value = null; // rebuild state from plain data / invalidate cache entry
} Prevention
- Persist plain data, not promise objects, so payloads stay unserialize-able across upgrades.
- Invalidate old caches when upgrading a library that adds serialization guards.
- Use allowed_classes options when unserializing untrusted data.
When it happens
Trigger: Calling unserialize() on a payload representing a class using NonSerializableTrait (e.g. data cached before upgrading, payloads from another system, or attacker-supplied strings naming promise classes), or unserializing data that embeds a promise object.
Common situations: Restoring old cache entries that predate the guard; unserialize() of session data after the library started using the trait; deserializing queued jobs that accidentally embedded promises.
Related errors
- should never be serialized
- 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/d9c1620cf3258b89.
Report an issue: GitHub.
Appendix: source
Thrown at src/NonSerializableTrait.php:19
<?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)