passbolt/passbolt_api · error · CustomValidationException

Could not save secret revision

Error message

Could not save secret revision

What it means

Thrown by CreateSecretRevisionsService::createFirstRevision() when SecretRevisions->save($secretRevision) returns falsy during creation of a resource's first secret revision; CustomValidationException carries the entity errors (HTTP 422-style validation response). Note the errors are read from the post-save (possibly replaced) entity, which can mask the real errors.

Solutions

  1. Capture $errors = $this->SecretRevisions->getErrors() before save and log them to see which field failed
  2. Ensure the associated Resource/Secret entities are persisted and have valid IDs before creating the revision
  3. Run pending migrations so the secret_revisions table exists with correct constraints
  4. Verify the payload passed into the revision (data, secrets association) satisfies the table's validation rules

Example fix

// before
$secretRevision = $this->SecretRevisions->save($secretRevision);
if (!$secretRevision) {
    throw new CustomValidationException(__('Could not save secret revision'), $secretRevision->getErrors());
}
// after
$errors = $secretRevision->getErrors();
$secretRevision = $this->SecretRevisions->save($secretRevision);
if (!$secretRevision) {
    throw new CustomValidationException(__('Could not save secret revision'), $errors);
}
Defensive patterns

Strategy: try-catch

Validate before calling

$errors = $secretRevision->getErrors();
if ($errors) { throw new CustomValidationException(__('Invalid secret revision'), $errors); }
if (!$resource->id || !$secret->id) { throw new \InvalidArgumentException('Resource and secret must be persisted first'); }

Try / catch

try {
    $revision = $service->createFirstRevision($uac, $resourceId, $secrets);
} catch (CustomValidationException $e) {
    Log::error('Revision validation failed', ['errors' => $e->getErrors()]);
}

Prevention

When it happens

Trigger: Creating the first secret revision for a new resource where the SecretRevisions table rejects the entity — invalid foreign keys (resource_id/secret_id), missing required fields, association save (secrets) failure, or rules such as unique revision constraints failing.

Common situations: Resource or secret not persisted before the revision save (ordering bug); data violating SecretRevisions validation rules (empty data, bad timestamps); database constraint violations surfaced as validation errors; schema drift after missing migrations.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

Thrown at plugins/PassboltCe/SecretRevisions/src/Service/CreateSecretRevisionsService.php:79

            'accessibleFields' => [
                'resource_id' => true,
                'resource_type_id' => true,
                'created_by' => true,
                'modified_by' => true,
            ],
        ]);
        $secretRevision->secrets = $resource->secrets;
        // For performance, we explicitly set the fields of the secrets as non-accessible, so the data for example
        // of the secret is not persisted again.
        // The goal here is only to persist the secret_revision_id field
        foreach ($secretRevision->secrets as $secret) {
            $secret->setAccess('*', false);
        }
        $secretRevision->setDirty('secrets');
        /** @var \Passbolt\SecretRevisions\Model\Entity\SecretRevision $secretRevision */
        $secretRevision = $this->SecretRevisions->save($secretRevision);
        if (!$secretRevision) {
            throw new CustomValidationException(__('Could not save secret revision'), $secretRevision->getErrors());
        }

        return $secretRevision;
    }

    /**
     *  - Soft delete the previous secret revision and secrets
     *  - creates a secret revision on resource update and associates it to:
     * - the resource passed as parameter
     * - the secrets associated to this resource
     *
     * @param \App\Model\Entity\Resource $resource the resource being saved
     * @return \Passbolt\SecretRevisions\Model\Entity\SecretRevision
     */
    public function createNewRevision(Resource $resource): ?SecretRevision
    {
        if (empty($resource->secrets)) {
            return null;

View on GitHub (pinned to 31c1bbc10f)