passbolt/passbolt_api · error · InternalErrorException

No user found for the personal tag id

Error message

No user found for the personal tag id: "{0}".

What it means

Personal tags in V5 must be encrypted for the owning user. migratePersonal expects the tag's 'users' contains to carry at least one user; when the association is empty it throws InternalErrorException naming the tag id. This indicates inconsistent data — a personal tag with no owning user.

Solutions

  1. Add contain('Users') (or the required containment) to the query that fetches tags for migration.
  2. Clean up orphaned personal tags (delete tags with no tag records) before migrating.
  3. Re-associate an owner or delete the dangling tag, then rerun migration.
  4. Audit with SQL: select tags without matching tag_records/users rows.

Example fix

// before
$tags = $this->Tags->find()->all();
// after
$tags = $this->Tags->find()->contain(['Users'])->where(['Tags.is_shared' => false])->all();
Defensive patterns

Strategy: validation

Validate before calling

$orphanTags = $tagsTable->find()
    ->contain(['Users'])
    ->where(['is_shared' => false])
    ->all()
    ->filter(fn($t) => count($t->get('users') ?? []) === 0);

Type guard

function hasOwner(?Tag $tag): bool {
    $users = $tag?->get('users');
    return is_array($users) && count($users) > 0 && $users[0] !== null;
}

Try / catch

try {
    $service->migrate($uac, $batch);
} catch (InternalErrorException $e) {
    if (str_contains($e->getMessage(), 'No user found for the personal tag')) {
        $this->log('Orphaned tag; exclude from batch and clean up');
    } else { throw $e; }
}

Prevention

When it happens

Trigger: Migrating a personal (non-shared) tag whose _tag_records/users join data is missing or the containment was not applied on the query feeding the migration.

Common situations: Orphaned tags left by deleted users without cleanup; migration batch query missing ->contain('Users'); partially deleted resource-tag relations from older versions.

Understand the failure class

Background: Record Not Found Errors: "not found", RecordNotFound, and "was not found" — what they mean and how to fix them — 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/a6a2f1a5ca5f47b7. Report an issue: GitHub.

Appendix: source

Thrown at plugins/PassboltEe/Tags/src/Service/Metadata/MigrateAllV4TagsToV5Service.php:200

            'metadata' => $metadataEncrypted,
            'metadata_key_id' => $metadataKey->id,
            'metadata_key_type' => MetadataKey::TYPE_SHARED_KEY,
            'is_shared' => true,
        ]);
    }

    /**
     * @param \Passbolt\Tags\Model\Dto\MetadataTagDto $dto DTO.
     * @param \Passbolt\Tags\Model\Entity\Tag $tag Tag entity.
     * @return void
     */
    private function migratePersonal(MetadataTagDto $dto, Tag $tag): void
    {
        $metadataArray = $dto->getClearTextMetadata();
        $users = $tag->get('users');

        if (!is_array($users) || count($users) < 1) {
            throw new InternalErrorException(__('No user found for the personal tag id: "{0}".', $tag->id));
        }

        /** @var \App\Model\Entity\User $user */
        $user = $users[0];
        if (is_null($user)) {
            throw new InternalErrorException(__('User contain data missing for the tag id: "{0}".', $tag->id));
        }
        if (is_null($user->gpgkey)) {
            $msg = __('No OpenPGP key found for the user.') . ' ';
            $msg .= __('The metadata could not be encrypted with the user id: {0}.', $user->id);
            throw new InternalErrorException($msg);
        }

        try {
            $gpg = OpenPGPBackendFactory::get();
            $gpg->clearKeys();
            $gpg = $this->setSignKeyWithServerKey($gpg);
            $gpg = $this->setEncryptKeyWithUserKey($gpg, $user->gpgkey);

View on GitHub (pinned to 31c1bbc10f)