flarum/framework · warning · ValidationException

This erasure request has already been processed.

Error message

This erasure request has already been processed.

What it means

ConfirmErasureController rejects confirmation if the erasure request status is already STATUS_PROCESSED or STATUS_MANUAL, throwing this ValidationException — the request has reached a terminal state and cannot be confirmed again.

Solutions

  1. Do nothing further — the erasure request is already completed; no action is needed.
  2. If a new erasure is required, submit a fresh erasure request.
  3. Check erasure_requests.status in the database to confirm the terminal state before retrying.

Example fix

null
Defensive patterns

Strategy: validation

Validate before calling

if (in_array($erasureRequest->status, [ErasureRequest::STATUS_PROCESSED, ErasureRequest::STATUS_MANUAL])) { /* skip confirmation; show 'already processed' */ }

Try / catch

try { $http->get($confirmUrl); } catch (ValidationException $e) { if (isset($e->getErrors()['request'])) { /* treat as no-op / show status */ } }

Prevention

When it happens

Trigger: Clicking the erasure confirmation link (or re-submitting it) after the request was already fully processed (STATUS_PROCESSED) or handled manually (STATUS_MANUAL).

Common situations: Double-clicking the emailed link; re-visiting an old confirmation email after the request was completed; an admin already processed the request in the backend.

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


AI-assisted analysis of flarum/framework@4b939f6853 (2026-09-15). Data as JSON: /api/errors/a713362a3bcedabc. Report an issue: GitHub.

Appendix: source

Thrown at extensions/gdpr/src/Http/Controller/ConfirmErasureController.php:51

    {
        $actor = RequestUtil::getActor($request);
        $token = Arr::get($request->getQueryParams(), 'token');

        /** @var ErasureRequest $erasureRequest */
        $erasureRequest = ErasureRequest::query()
            ->with('user')
            ->where('verification_token', $token)
            ->firstOrFail();

        /**
         * @TODO: the token is enough to confirm the erasure request. We should not require the user to be logged in.
         */
        if ($erasureRequest->user->isNot($actor) && ! $actor->isGuest()) {
            throw new ValidationException(['user' => 'Erase requests cannot be confirmed by different users.']);
        }

        if (in_array($erasureRequest->status, [ErasureRequest::STATUS_PROCESSED, ErasureRequest::STATUS_MANUAL])) {
            throw new ValidationException(['request' => 'This erasure request has already been processed.']);
        }

        $ip = $request->getAttribute('ipAddress');

        $erasureRequest->user_confirmed_at = Carbon::now();
        $erasureRequest->status = ErasureRequest::STATUS_USER_CONFIRMED;
        $erasureRequest->cancelled_at = null;
        $erasureRequest->verification_token = null;
        $erasureRequest->confirmation_ip = $ip;
        $erasureRequest->save();

        // Attribute to the request's owner: confirmation may arrive via the
        // emailed token while logged out, so $actor can be a guest.
        $this->events->dispatch(new ErasureConfirmed($erasureRequest->user, $erasureRequest));

        return new RedirectResponse($this->url->to('forum')->base().'?erasureRequestConfirmed=1');
    }
}

View on GitHub (pinned to 4b939f6853)