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

  1. Check the database is reachable and migrations are up to date (run pending migrations)
  2. Look for a duplicate metadata_private_keys row created concurrently for the same (metadata_key_id, user_id); deduplicate and retry
  3. Retry the share operation — it is safe to re-run once the conflicting row is handled
  4. 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

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


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)