{"record":{"id":"146c0e3ca6f67339","repo":"guzzle/promises","slug":"cannot-resolve-a-fulfilled-promise","errorCode":null,"errorMessage":"Cannot resolve a fulfilled promise","messagePattern":"Cannot resolve a fulfilled promise","errorType":"exception","errorClass":"LogicException","httpStatus":null,"severity":"error","filePath":"src/FulfilledPromise.php","lineNumber":98,"sourceCode":"    public function otherwise(callable $onRejected): PromiseInterface\n    {\n        return $this->then(null, $onRejected);\n    }\n\n    public function wait(bool $unwrap = true)\n    {\n        return $unwrap ? $this->value : null;\n    }\n\n    public function getState(): string\n    {\n        return self::FULFILLED;\n    }\n\n    public function resolve($value = null): void\n    {\n        if ($value !== $this->value) {\n            throw new \\LogicException('Cannot resolve a fulfilled promise');\n        }\n    }\n\n    public function reject($reason): void\n    {\n        throw new \\LogicException('Cannot reject a fulfilled promise');\n    }\n\n    public function cancel(): void\n    {\n        // pass\n    }\n}\n","sourceCodeStart":80,"sourceCodeEnd":112,"githubUrl":"https://github.com/guzzle/promises/blob/42118e66a53c492effaf92bc357e931985d5c6f9/src/FulfilledPromise.php#L80-L112","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\n$fulfilled->resolve('different value'); // LogicException\n// after\n$next = new \\GuzzleHttp\\Promise\\Promise();\n$next->resolve('different value');","handlingStrategy":"try-catch","validationCode":"// Cannot inspect state publicly on FulfilledPromise; instead ensure the promise is\n// freshly created or only settled by a single owner before calling resolve().","typeGuard":"function canResolve(\\GuzzleHttp\\Promise\\PromiseInterface $p): bool\n{\n    return $p instanceof \\GuzzleHttp\\Promise\\Promise && $p->getState() === 'pending';\n}","tryCatchPattern":"try {\n    $promise->resolve($value);\n} catch (\\LogicException $e) {\n    // already settled; ignore or create a new promise\n    $promise = new \\GuzzleHttp\\Promise\\FulfilledPromise($value);\n}","preventionTips":["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."],"tags":["promises","state","immutability","guzzle"],"backgroundTag":"invalid-state-transition","analyzedSha":"42118e66a53c492effaf92bc357e931985d5c6f9","analyzedAt":"2026-09-14T00:10:14.865Z","contentChangedAt":"2026-09-14T00:10:14.865Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}