passbolt/passbolt_api · error · InternalErrorException

Could not save permission history.

Error message

Could not save permission history.

What it means

Thrown by PermissionsHistoryTable::create() when CakePHP's save() returns false for a permission_history record that already passed validation. It signals an unexpected persistence failure (database constraint, connection, or afterSave rule) rather than bad input, so it surfaces as a 500 InternalErrorException.

Solutions

  1. Verify the referenced aco_foreign_key and aro_foreign_key exist in their parent tables before calling create()
  2. Run pending migrations (passbolt migrate) and confirm the permissions_history table exists with correct columns
  3. Check the database error log / CakePHP error.log for the underlying SQL failure and fix the constraint
  4. Re-run the permission change transaction; if transient (lock/timeout), retry after the DB recovers

Example fix

// before
$permissionHistory = $this->PermissionsHistory->create($data); // throws 500 on save failure
// after
$aco = $this->Aco->findById($data['aco_foreign_key'])->first();
$aro = $this->Aro->findById($data['aro_foreign_key'])->first();
if (!$aco || !$aro) {
    throw new BadRequestException(__('Referenced aco/aro does not exist.'));
}
$permissionHistory = $this->PermissionsHistory->create($data);
Defensive patterns

Strategy: try-catch

Validate before calling

use Cake\Validation\Validation;
$ok = Validation::uuid($data['aco_foreign_key'] ?? '') && Validation::uuid($data['aro_foreign_key'] ?? '');

Type guard

if (!is_array($data) || empty($data['aco_foreign_key']) || empty($data['aro_foreign_key'])) { return false; }

Try / catch

try {
    $log = $this->PermissionsHistory->create($data);
} catch (\Cake\Http\Exception\InternalErrorException $e) {
    $this->log('Permission history save failed: ' . $e->getMessage());
    // inspect DB logs, retry or surface a 500 with context
}

Prevention

When it happens

Trigger: Calling PermissionsHistoryTable::create($data) where the entity validates but $this->save($log) returns falsy — e.g. a database-level constraint violation (duplicate/missing aco_foreign_key or aro_foreign_key not present in aco/aro tables), storage engine failure, or an event listener aborting the save.

Common situations: Orphaned permission data after a hard delete (permissions referencing deleted resources/users), missing or broken permissions_history table after a skipped migration, database disk-full or lock issues, and plugin (Passbolt/Log) schema drift between versions.

Related errors


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

Appendix: source

Thrown at plugins/PassboltCe/Log/src/Model/Table/PermissionsHistoryTable.php:212

     */
    public function create(array $data): PermissionHistory
    {
        // Check validation rules.
        $log = $this->buildEntity($data);
        if ($log->getErrors()) {
            throw new ValidationException(__('Could not validate permission history data.', true), $log, $this);
        }

        $permissionHistory = $this->save($log);

        // Check for validation errors. (associated models too).
        if ($log->getErrors()) {
            throw new ValidationException(__('Could not validate permission history data.'), $permissionHistory, $this);
        }

        // Check for errors while saving.
        if (!$permissionHistory) {
            throw new InternalErrorException('Could not save permission history.');
        }

        return $permissionHistory;
    }
}

View on GitHub (pinned to 31c1bbc10f)