passbolt/passbolt_api · error · InternalErrorException

Unable to migrate folder ID

Error message

Unable to migrate folder ID: {0} to V5. {exceptionMessage}

What it means

Thrown by updateFolder() when patching and saving a folder with its V5 metadata fails (validation or persistence error via saveOrFail). It wraps the original exception's message into an InternalErrorException with a 500 status, and migratePersonal/migrateShared propagate it. The migration service catches it per-folder and records the error in the migration result, so the migration aborts for that folder only.

Solutions

  1. Read the appended exception message in the error output; it names the exact validation field or database error that made saveOrFail() fail.
  2. Fix the underlying database issue (disk space, connection, lock) and re-run the migration command.
  3. Verify the folder row is consistent: metadata fields nullable constraints, metadata_key_id references an existing metadata_keys row.
  4. Ensure the metadata plugin migrations are up to date (`ddev refresh` or run migrations) before migrating.
  5. If a specific folder is corrupt, repair or delete/recreate it, then re-run migration.

Example fix

// diagnosis: appended message says "_metadata_key_id: The provided value is invalid"
// before
$metadata_key_id => $metadataKey->id (deleted/expired key row)
// after
$metadataKey = $this->getMetadataKeyForEncryption(); // re-fetch latest active key and re-run migration
Defensive patterns

Strategy: try-catch

Validate before calling

// before running the migration
$foldersTable = \Cake\ORM\Locator\LocatorAwareTrait::fetchTable('Folders');
$broken = $foldersTable->find()->where(function ($exp, $q) {
    return $exp->isNull('metadata_key_id'); // or other preconditions
})->count();

Type guard

if (!($folder instanceof \Passbolt\Folders\Model\Entity\Folder) || $folder->id === null) { return; }

Try / catch

try {
    (new MigrateAllV4FoldersToV5Service())->migrate();
} catch (\Cake\Http\Exception\InternalErrorException $e) {
    // inspect $e->getMessage() for the wrapped validation/db error, fix, re-run
}

Prevention

When it happens

Trigger: Calling migratePersonal() or migrateShared() on a V4 folder leads to updateFolder(), where FoldersTable->patchEntity(..., ['validate' => 'v5']) produces validation errors (e.g. metadata, metadata_key_id or metadata_key_type invalid under the v5 validation ruleset), or saveOrFail() fails (database constraint/write failure).

Common situations: Database write failures during a bulk V4-to-V5 folder migration (disk full, lock timeout, connection dropped); the v5 validation ruleset rejecting the patched data such as a null/empty metadata field or an invalid metadata_key_id UUID; running the metadata migration command on an instance with partially applied metadata schema changes.

Related errors


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

Appendix: source

Thrown at plugins/PassboltCe/Metadata/src/Service/Migration/MigrateAllV4FoldersToV5Service.php:239

     * @return void
     */
    private function updateFolder(Folder $folder, array $data): void
    {
        try {
            $folder = $this->Folders->patchEntity($folder, $data, [
                'accessibleFields' => [
                    'name' => true,
                    'metadata' => true,
                    'metadata_key_id' => true,
                    'metadata_key_type' => true,
                ],
                'validate' => 'v5',
            ]);
            $this->Folders->saveOrFail($folder, ['checkRules' => false]);
        } catch (Exception $exception) {
            $msg = __('Unable to migrate folder ID: {0} to V5.', $folder->id);
            $msg .= ' ' . $exception->getMessage();
            throw new InternalErrorException($msg, 500, $exception);
        }
    }

    /**
     * @return \Passbolt\Metadata\Model\Entity\MetadataKey
     * @throws \Cake\Datasource\Exception\RecordNotFoundException When there is no metadata key record.
     */
    private function getMetadataKeyForEncryption(): MetadataKey
    {
        /** @var \Passbolt\Metadata\Model\Table\MetadataKeysTable $metadataKeysTable */
        $metadataKeysTable = $this->fetchTable('Passbolt/Metadata.MetadataKeys');

        /** @var \Passbolt\Metadata\Model\Entity\MetadataKey $metadataKey */
        $metadataKey = $metadataKeysTable->getLatestActiveKey();

        return $metadataKey;
    }

View on GitHub (pinned to 31c1bbc10f)