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
- Verify the metadata private key entities passed in were freshly fetched and their id fields are populated before calling delete()
- Check for concurrent deletions (jobs, other requests) targeting the same user's metadata private keys
- Log the extracted $metadataPrivateKeysIds and confirm rows with those ids exist in the metadata_private_keys table
- 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
- Always pass freshly fetched entities with populated ids
- Run the delete inside a transaction with the fetch
- Check idempotency: treat zero-row deletes for already-deleted keys as success
- Log extracted ids for post-mortem on races
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
- The user metadata session keys could not be deleted.
- Could not delete the group
- Could not delete the user
- Could not save the rbacs, please try again later.
- Could not save the setting, please try again later.
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)