{"record":{"id":"dc461bf1b3916dfd","repo":"passbolt/passbolt_api","slug":"the-request-is-already-completed","errorCode":null,"errorMessage":"The request is already completed.","messagePattern":"The request is already completed\\.","errorType":"http","errorClass":"Cake\\Http\\Exception\\BadRequestException","httpStatus":400,"severity":"error","filePath":"plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryRequests/AccountRecoveryRequestGetService.php","lineNumber":174,"sourceCode":"        if (!Validation::uuid($requestId)) {\n            throw new BadRequestException(__('The request id is invalid.'));\n        }\n        try {\n            $where = [\n                'id' => $requestId,\n                'user_id' => $userEntity->id,\n                'authentication_token_id' => $tokenEntity->id,\n            ];\n            /** @var \\Passbolt\\AccountRecovery\\Model\\Entity\\AccountRecoveryRequest $requestEntity */\n            $requestEntity = $this->AccountRecoveryRequests->find()->where($where)->firstOrFail();\n        } catch (RecordNotFoundException $exception) {\n            $this->onRequestDoesNotExist($requestId, $userId, $clientIp ?? '0.0.0.0');\n            throw new NotFoundException(__('The account recovery request could not be found.'));\n        }\n\n        // Assert request is not already completed\n        if ($requestEntity->isCompleted()) {\n            throw new BadRequestException(__('The request is already completed.'));\n        }\n        // Assert token is not expired. If so, deactivate the token, reject the request and throw an exception\n        if ($tokenEntity->isExpired()) {\n            $requestEntity->set('status', AccountRecoveryRequest::ACCOUNT_RECOVERY_REQUEST_REJECTED);\n            $requestEntity->setAccess('status', true);\n            $this->AccountRecoveryRequests->saveOrFail($requestEntity);\n            $tokenService->getActiveNotExpiredOrFail($token, $userId, AuthenticationToken::TYPE_RECOVER);\n        }\n\n        return $requestEntity;\n    }\n\n    /**\n     * @param \\Passbolt\\AccountRecovery\\Model\\Entity\\AccountRecoveryRequest $requestEntity entity\n     * @return array\n     */\n    public function decorateResults(AccountRecoveryRequest $requestEntity): array\n    {","sourceCodeStart":156,"sourceCodeEnd":192,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/AccountRecovery/src/Service/AccountRecoveryRequests/AccountRecoveryRequestGetService.php#L156-L192","documentation":"BadRequestException from getNotCompletedOrFail when the matched account recovery request is already in a completed status (approved, rejected or completed). Completed requests cannot be fetched/processed again through this path.","triggerScenarios":"GET /account-recovery/requests/{id} for a request whose status has already transitioned out of 'pending' — e.g. an admin already responded, or a previous recovery attempt finalized it.","commonSituations":"Client double-submits or refreshes after the request was completed; admin approved/rejected while the user's client retries; user retries recovery after already completing it once.","solutions":["Start a new account recovery request (new token) instead of reusing the completed one","Check the request's `status` before calling; only 'pending' requests are fetchable via this method","If recovery was rejected/completed, follow the returned outcome (e.g. complete setup or normal login)"],"exampleFix":"// before\ngetNotCompletedOrFail($requestId, ...) // status = 'approved' -> 400\n// after\ncreate a fresh request via AccountRecoveryRequestCreateService, then get the new request id","handlingStrategy":"validation","validationCode":"$request = $this->AccountRecoveryRequests->get($requestId); if ($request->isCompleted()) { // skip; start a new recovery flow instead }","typeGuard":"null","tryCatchPattern":"try { $request = $service->get($requestId, $userId, $token); } catch (BadRequestException $e) { if (str_contains($e->getMessage(), 'already completed')) { startNewRecoveryFlow(); } }","preventionTips":["Track request status client-side; disable retries once completed","Treat this as terminal: create a new request rather than reusing it","Avoid double submission / auto-refresh of the recovery completion step"],"tags":["account-recovery","state-validation","bad-request","idempotency"],"backgroundTag":"invalid-state-transition","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T09:17:21.228Z"}