passbolt/passbolt_api · error · InternalErrorException

The user metadata private keys could not be deleted.

Error message

The user metadata private keys could not be deleted.

What it means

This InternalErrorException is thrown by deleteMetadataPrivateKeys when a deleteAll over the user's metadata private key records affects zero rows. It signals that the deletion of metadata private keys (required when removing a user's metadata key data) silently failed, so the service aborts the delete operation with an internal error rather than leaving inconsistent state.

Solutions

  1. Verify the metadata private key entities passed in were freshly fetched and their id fields are populated before calling delete()
  2. Check for concurrent deletions (jobs, other requests) targeting the same user's metadata private keys
  3. Log the extracted $metadataPrivateKeysIds and confirm rows with those ids exist in the metadata_private_keys table
  4. Wrap the whole delete() flow in a transaction so fetch and delete are consistent

Example fix

// before
$result = $metadataPrivateKeysTable->deleteAll(['id IN' => $metadataPrivateKeysIds]);
if ($result <= 0) {
    throw new InternalErrorException(__('The user metadata private keys could not be deleted.'));
}
// after
$metadataPrivateKeysIds = Hash::extract($metadataPrivateKeys, '{n}.id');
if (empty($metadataPrivateKeysIds)) {
    return; // nothing to delete is not a failure
}
$result = $metadataPrivateKeysTable->deleteAll(['id IN' => $metadataPrivateKeysIds]);
if ($result <= 0) {
    throw new InternalErrorException(__('The user metadata private keys could not be deleted.'));
}
Defensive patterns

Strategy: try-catch

Validate before calling

$ids = Hash::extract($metadataPrivateKeys, '{n}.id');
if (empty($ids) || !TableRegistry::getTableLocator()->get('MetadataPrivateKeys')->exists(['id IN' => $ids])) {
    // skip or reconcile before deleting
}

Type guard

function hasDeletableIds(array $entities): bool {
    $ids = Hash::extract($entities, '{n}.id');
    return !empty($ids) && array_reduce($ids, fn($ok, $id) => $ok && is_string($id) && Validation::uuid($id), true);
}

Try / catch

try {
    $service->delete($user);
} catch (InternalErrorException $e) {
    // inspect metadata_private_keys table state, retry with fresh entities
    $this->log($e->getMessage(), 'error');
}

Prevention

When it happens

Trigger: Calling delete() on UserMetadataKeysDeleteService for a user whose $metadataPrivateKeys entities were fetched but whose ids no longer exist in the metadata_private_keys table at deleteAll time (e.g. concurrent deletion, transaction isolation mismatch), or passing entities with missing/invalid ids so Hash::extract yields ids matching nothing.

Common situations: Race conditions where two requests delete the same user's metadata keys; orphaned/soft-deleted rows filtered out by deleteAll; ids extracted from stale entities after a prior partial delete.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/5f9bf15adbaaf4fa. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltCe/Metadata/src/Service/UserMetadataKeysDeleteService.php:68

    private function deleteMetadataPrivateKeys(string $userId): void
    {
        /** @var \Passbolt\Metadata\Model\Table\MetadataPrivateKeysTable $metadataPrivateKeysTable */
        $metadataPrivateKeysTable = $this->fetchTable('Passbolt/Metadata.MetadataPrivateKeys');

        $metadataPrivateKeys = $metadataPrivateKeysTable
            ->unhydratedFind()
            ->select(['id'])
            ->where(['user_id' => $userId])
            ->toArray();
        if (empty($metadataPrivateKeys)) {
            // Nothing to delete
            return;
        }

        $metadataPrivateKeysIds = Hash::extract($metadataPrivateKeys, '{n}.id');
        $result = $metadataPrivateKeysTable->deleteAll(['id IN' => $metadataPrivateKeysIds]);
        if ($result <= 0) {
            throw new InternalErrorException(__('The user metadata private keys could not be deleted.'));
        }
    }

    /**
     * @param string $userId User identifier.
     * @return void
     * @throws \Cake\Http\Exception\InternalErrorException If data is not deleted.
     */
    private function deleteMetadataSessionKeys(string $userId): void
    {
        /** @var \Passbolt\Metadata\Model\Table\MetadataSessionKeysTable $metadataSessionKeysTable */
        $metadataSessionKeysTable = $this->fetchTable('Passbolt/Metadata.MetadataSessionKeys');

        $metadataSessionKeys = $metadataSessionKeysTable
            ->unhydratedFind()
            ->select(['id'])
            ->where(['user_id' => $userId])
            ->toArray();

View on GitHub (pinned to 31c1bbc10f)