passbolt/passbolt_api · warning · NotFoundException

The metadata key has already been deleted.

Error message

The metadata key has already been deleted.

What it means

Thrown when the targeted metadata key already has its 'deleted' flag set, i.e. a previous delete already succeeded. The service rejects the redundant delete with NotFoundException (HTTP 404) to keep delete idempotent-unfriendly and explicit.

Solutions

  1. Check the key's deleted flag (or re-fetch the key) before calling delete
  2. Treat 404 with this message as 'already done' and skip rather than fail
  3. Avoid retrying delete blindly on timeout — first query the key state

Example fix

// before
await deleteMetadataKey(id);
await deleteMetadataKey(id); // second call throws
// after
if (!isKeyDeleted(id)) {
  await deleteMetadataKey(id);
}
Defensive patterns

Strategy: validation

Validate before calling

const key = await getMetadataKey(id);
if (key.deleted) return; // already deleted, skip

Try / catch

try {
  await deleteMetadataKey(id);
} catch (e) {
  if (e.status === 404 && /already been deleted/.test(e.message)) return;
  throw e;
}

Prevention

When it happens

Trigger: DELETE /metadata/keys/<id> executed twice; a retry after a timed-out-but-successful first delete; another admin deleted the key between the caller's fetch and the delete call.

Common situations: Automated scripts or CI pipelines that re-run a migration step without checking current key state; double-click / duplicate form submission in tooling built on the API.

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 passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/634a1ca1ea389e6e. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltCe/Metadata/src/Service/MetadataKey/MetadataKeyDeleteService.php:66

        $uac->assertIsAdmin();

        if (!Validation::uuid($id)) {
            throw new BadRequestException(__('The metadata key ID should be a valid UUID.'));
        }

        $metadataKeysTable = $this->fetchTable('Passbolt/Metadata.MetadataKeys');

        // Assert the key exist
        try {
            /** @var \Passbolt\Metadata\Model\Entity\MetadataKey $metadataKey */
            $metadataKey = $metadataKeysTable->get($id);
        } catch (RecordNotFoundException $exception) { // @phpstan-ignore-line
            throw new NotFoundException(__('The metadata key does not exist or has been deleted.'), 404, $exception);
        }

        // Assert the key is not already deleted
        if ($metadataKey->isDeleted()) {
            throw new NotFoundException(__('The metadata key has already been deleted.'));
        }

        // Assert they key was previously marked as expired
        if (!$metadataKey->isExpired()) {
            throw new BadRequestException(__('The metadata key should be marked as expired first.'));
        }

        // Assert the key is not used by folders, resources, tags, etc.
        if ((new MetadataKeyAssertUsageService())->isKeyInUse($metadataKey->get('id'))) {
            $msg = __('The metadata key is still in use, migrate the remaining items to the new key first.');
            throw new BadRequestException($msg);
        }

        // Patch the key deleted field with the current time
        $options['accessibleFields'] = ['deleted' => true, 'modified_by' => true];
        $patch = ['deleted' => DateTime::now(), 'modified_by' => $uac->getId()];
        $metadataKey = $metadataKeysTable->patchEntity($metadataKey, $patch, $options);
        if ($metadataKey->getErrors()) {

View on GitHub (pinned to 31c1bbc10f)