passbolt/passbolt_api · error · InternalErrorException

No permission found for folder ID

Error message

No permission found for folder ID {0}

What it means

The V4-to-V5 folder migration requires every folder to have at least one permission (a personal folder needs its owner's permission to know whose key encrypts the metadata). If the loaded folder's permissions collection is empty, an InternalErrorException naming the folder ID is thrown from migrate().

Solutions

  1. Delete or repair the orphaned folder(s) — restore permissions for the folder identified in the message.
  2. Re-grant an owner permission to the folder, then re-run the migration.
  3. Skip folders with no permissions (log their IDs) so the batch can continue and address them manually.
  4. Audit with SQL: folders left-joined to permissions where permissions.id IS NULL.

Example fix

// before: any folder is processed
$folders = $foldersTable->find()->contain('Permissions')->all();
// after: require at least one permission
$folders = $foldersTable->find()
    ->innerJoinWith('Permissions')
    ->distinct()
    ->contain('Permissions')->all();
Defensive patterns

Strategy: validation

Validate before calling

$orphanFolders = TableRegistry::getTableLocator()->get('Folders')
    ->find()
    ->notMatching('Permissions')
    ->all(); // repair or delete these before migrating

Type guard

if (count($folder->permissions) === 0) { continue; } // skip and log

Try / catch

try {
    $service->migrate($uac);
} catch (\Cake\Http\Exception\InternalErrorException $e) {
    if (str_starts_with($e->getMessage(), 'No permission found for folder')) {
        // repair the folder identified in the message
    }
}

Prevention

When it happens

Trigger: Migrating a folder that exists in the folders table but has zero rows in permissions — orphaned folder data from cascading permission deletions, manual DB edits, or restore inconsistencies.

Common situations: Databases where permissions were removed without deleting the folder; partially restored backups; folders inherited visibility through ACL cleanup gone wrong.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


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

Appendix: source

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

            ->toArray();

        if (empty($folders)) {
            $this->addError(['error_message' => __('No folders to migrate.')]);

            return $this->getResult();
        }

        foreach ($folders as $folder) {
            $dto = MetadataFolderDto::fromArray($folder->toArray());

            try {
                if ($dto->isV5()) {
                    $msg = __('Folder ID "{0}" is already V5', $folder->id);
                    throw new InternalErrorException($msg);
                }
                if (count($folder->permissions) === 0) {
                    $msg = __('No permission found for folder ID {0}', $folder->id);
                    throw new InternalErrorException($msg);
                }

                if (count($folder->permissions) === 1) {
                    $this->migratePersonal($dto, $folder);
                } else {
                    $this->migrateShared($dto, $folder);
                }
                $this->addMigrated($folder);
            } catch (Exception $e) {
                // Continue with next resource if any error
                $error = ['folder_id' => $folder->id, 'error_message' => $e->getMessage()];
                if (Configure::read('debug')) {
                    $error['trace'] = $e->getTraceAsString();
                }
                $this->addError($error);
            }
        }

View on GitHub (pinned to 31c1bbc10f)