guzzle/promises · error · LogicException
Cannot resolve a fulfilled promise
Error message
Cannot resolve a fulfilled promise
What it means
FulfilledPromise::resolve() may only be called with the identical value the promise already holds (=== comparison); any other value is a no-op that is not allowed. The library throws this LogicException because a settled promise is immutable: silently swapping its value would break the guarantee that every then() observer saw the same result. Note resolve($value) with $value === $this->value is intentionally permitted as a no-op.
Solutions
- Do not resolve an already-settled promise; create a new Promise and resolve that instead.
- Guard with a state check: only call resolve() on promises that can still be settled.
- If you need a different outcome, reject the old promise is also impossible - build a fresh promise chain.
- Ensure only one owner/path is responsible for settling each promise.
Example fix
// before
$fulfilled->resolve('different value'); // LogicException
// after
$next = new \GuzzleHttp\Promise\Promise();
$next->resolve('different value'); Defensive patterns
Strategy: try-catch
Validate before calling
// Cannot inspect state publicly on FulfilledPromise; instead ensure the promise is // freshly created or only settled by a single owner before calling resolve().
Type guard
function canResolve(\GuzzleHttp\Promise\PromiseInterface $p): bool
{
return $p instanceof \GuzzleHttp\Promise\Promise && $p->getState() === 'pending';
} Try / catch
try {
$promise->resolve($value);
} catch (\LogicException $e) {
// already settled; ignore or create a new promise
$promise = new \GuzzleHttp\Promise\FulfilledPromise($value);
} Prevention
- Only settle promises you created and that are still pending.
- Ensure a single code path owns settlement of each promise.
- Never re-resolve promises reused across requests; create new ones per operation.
When it happens
Trigger: Calling ->resolve($v) on a FulfilledPromise whose stored value is not strictly equal (===) to $v, including the implicit default null when the promise was fulfilled with a non-null value or vice versa.
Common situations: Reusing one promise object across requests and re-resolving it; race conditions where two code paths settle the same promise; refactoring code that assumed promises are mutable; porting code from other promise libraries that ignore re-settling.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Cannot reject a fulfilled promise
- Cannot resolve a rejected promise
- Cannot reject a rejected promise
- Not enough promises to fulfill count
- You cannot create a FulfilledPromise with a promise.
AI-assisted analysis of guzzle/promises@42118e66a5 (2026-09-14).
Data as JSON: /api/errors/146c0e3ca6f67339.
Report an issue: GitHub.
Appendix: source
Thrown at src/FulfilledPromise.php:98
public function otherwise(callable $onRejected): PromiseInterface
{
return $this->then(null, $onRejected);
}
public function wait(bool $unwrap = true)
{
return $unwrap ? $this->value : null;
}
public function getState(): string
{
return self::FULFILLED;
}
public function resolve($value = null): void
{
if ($value !== $this->value) {
throw new \LogicException('Cannot resolve a fulfilled promise');
}
}
public function reject($reason): void
{
throw new \LogicException('Cannot reject a fulfilled promise');
}
public function cancel(): void
{
// pass
}
}
View on GitHub (pinned to 42118e66a5)