passbolt/passbolt_api · error · InternalErrorException

User contain data missing for the tag id

Error message

User contain data missing for the tag id: "{0}".

What it means

After fetching the owning user for a personal tag, migratePersonal checks that users[0] is not null before using it. A null entry means the contained user data is missing despite the association row count check passing, so an InternalErrorException naming the tag id is thrown.

Solutions

  1. Filter out tags whose contained user row no longer exists before migration.
  2. Repair referential integrity: delete tag records pointing at missing users.
  3. Verify the containment configuration for the migration query.
  4. Skip-and-log this tag per-tag and continue the batch, then remediate manually.

Example fix

// before
$user = $users[0];
// after
$user = $users[0] ?? null;
if ($user === null) { $this->skipped[] = $tag->id; return; }
Defensive patterns

Strategy: type-guard

Validate before calling

foreach ($tags as $tag) {
    $users = $tag->get('users');
    if (!is_array($users) || !isset($users[0])) { $skip[] = $tag->id; }
}

Type guard

function firstUser(?array $users): ?User {
    return (isset($users[0]) && $users[0] instanceof User) ? $users[0] : null;
}

Try / catch

try {
    $service->migrate($uac, $batch);
} catch (InternalErrorException $e) {
    if (str_contains($e->getMessage(), 'User contain data missing')) {
        $this->log('Null contained user; repair joins for tag');
    } else { throw $e; }
}

Prevention

When it happens

Trigger: The 'users' containment produced an array with a null first element — typically a broken join result or users association returning placeholder nulls when the referenced user row is absent.

Common situations: Tag records referencing soft/hard-deleted users; inconsistent contain() results on large batch queries; fixtures or restored backups with dangling user foreign keys.

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/2ed0c363214c62f4. Report an issue: GitHub.

Appendix: source

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

    /**
     * @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);
            $metadataClearText = json_encode($metadataArray, JSON_THROW_ON_ERROR);
            $metadataEncrypted = $gpg->encrypt($metadataClearText, true);
        } catch (Exception $exception) {
            $msg = $exception->getMessage() . ' ';
            $msg .= __('The metadata could not be encrypted with the user id: {0}.', $user->id);
            throw new InternalErrorException($msg, 500, $exception);

View on GitHub (pinned to 31c1bbc10f)