passbolt/passbolt_api · error · MetadataKeyShareException
The data could not be saved. Metadata key could not be…
Error message
{exception message} The data could not be saved. Metadata key could not be shared with user id: {0}. What it means
Wrapper error thrown when the database save of the validated MetadataPrivateKey entity for the target user throws. The original message is concatenated with 'The data could not be saved. Metadata key could not be shared with user id: {0}.' and rethrown as MetadataKeyShareException (HTTP 500). Note the save runs with checkRules => false, so this is almost always a persistence-layer failure, not validation.
Solutions
- Check the database is reachable and migrations are up to date (run pending migrations)
- Look for a duplicate metadata_private_keys row created concurrently for the same (metadata_key_id, user_id); deduplicate and retry
- Retry the share operation — it is safe to re-run once the conflicting row is handled
- Inspect DB logs for the underlying SQL error surfaced in the prefixed exception message
Example fix
// before: concurrent shares race on the same user
foreach ($userIds as $uid) { $service->shareMetadataKeyWithUser($users[$uid], $serverKey); }
// after: serialize the share per (key,user) to avoid duplicate-key races
$lock = acquireLock('metadata_share_' . $serverKey->metadata_key_id . '_' . $user->id);
if ($lock) { $service->shareMetadataKeyWithUser($user, $serverKey); releaseLock($lock); } Defensive patterns
Strategy: retry
Validate before calling
$this->metadataPrivateKeysTable->getSchema()->columns(); // ensure table exists post-migration
$dup = $metadataPrivateKeysTable->exists(['metadata_key_id' => $keyId, 'user_id' => $userId]);
if ($dup) { /* remove or skip before save */ } Try / catch
try {
$service->shareMetadataKeyWithUser($user, $serverKey);
} catch (MetadataKeyShareException $e) {
if (str_contains($e->getMessage(), 'could not be saved')) {
// duplicate-key/connection failure: dedupe then retry once
$this->dedupePrivateKeysRow($keyId, $user->id);
retryShare($user, $serverKey);
}
} Prevention
- Keep migrations current after every upgrade so metadata_private_keys schema matches code
- Serialize concurrent share operations per (metadata_key_id, user_id) with a lock
- Monitor database connectivity/lock timeouts during batch shares
- Design shares as idempotent so retries are safe
When it happens
Trigger: Calling shareMetadataKeyWithUsers/shareMetadataKeysWithUser when the INSERT into metadata_private_keys fails: database connection loss, unique/duplicate key constraint (row created concurrently), lock timeout, or a SQL error.
Common situations: Two admins sharing the same metadata key with the same user concurrently; MySQL/Postgres connection dropped mid-operation; migration missing so the table/column does not exist (after upgrade without running migrations).
Related errors
- Could not save the action.
- Could not save the action log.
- Could not save the entity history.
- New directory settings could not be saved.
- Could not parse the self registration settings found in…
AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17).
Data as JSON: /api/errors/963c41706b0956a3.
Report an issue: GitHub.
Appendix: source
Thrown at plugins/PassboltCe/Metadata/src/Service/MetadataKeyShareDefaultService.php:141
}
$msg = __('The OpenPGP key data is not valid.');
throw new ValidationException($msg, $userMetadataPrivateKey, $metadataPrivateKeysTable);
}
} catch (Exception $exception) {
$msg = $exception->getMessage() . ' ';
$msg .= __('The data could not be validated.') . ' ';
$msg .= __('Metadata key could not be shared with user id: {0}.', $user->id);
throw new MetadataKeyShareException($msg, 500, $exception);
}
// Save private key entity for the user
try {
$metadataPrivateKeysTable->save($userMetadataPrivateKey, ['checkRules' => false]);
} catch (Exception $exception) {
$msg = $exception->getMessage() . ' ';
$msg .= __('The data could not be saved.') . ' ';
$msg .= __('Metadata key could not be shared with user id: {0}.', $user->id);
throw new MetadataKeyShareException($msg, 500, $exception);
}
}
/**
* @inheritDoc
*/
public function onFailure(Exception $exception): void
{
Log::error($exception->getMessage());
if (Configure::read('debug')) {
Log::error($exception->getTraceAsString());
}
}
/**
* @param string $clearText private key object in json format
* @return void
*/View on GitHub (pinned to 31c1bbc10f)