{"record":{"id":"09abbdec7e5b9e0b","repo":"passbolt/passbolt_api","slug":"tag-id-0-is-already-v5","errorCode":null,"errorMessage":"Tag ID \"{0}\" is already V5","messagePattern":"Tag ID \"(.+?)\" is already V5","errorType":"http","errorClass":"InternalErrorException","httpStatus":500,"severity":"error","filePath":"plugins/PassboltEe/Tags/src/Service/Metadata/MigrateAllV4TagsToV5Service.php","lineNumber":101,"sourceCode":"            ->contain(['Users.Gpgkeys'])\n            ->where(['slug IS NOT NULL'])\n            ->all()\n            ->toArray();\n\n        if (count($tags) < 1) {\n            $this->addError(['error_message' => __('No tags to migrate.')]);\n\n            return $this->getResult();\n        }\n\n        /** @var \\Passbolt\\Tags\\Model\\Entity\\Tag $tag */\n        foreach ($tags as $tag) {\n            $dto = MetadataTagDto::fromArray($tag->toArray());\n\n            try {\n                if ($dto->isV5()) {\n                    $msg = __('Tag ID \"{0}\" is already V5', $tag->id);\n                    throw new InternalErrorException($msg);\n                }\n\n                if ($tag->is_shared) {\n                    $this->migrateShared($dto, $tag);\n                } else {\n                    $this->migratePersonal($dto, $tag);\n                }\n\n                $this->addMigrated($tag);\n            } catch (Exception $e) {\n                // Continue with next resource if any error\n                $error = ['tag_id' => $tag->id, 'error_message' => $e->getMessage()];\n                if (Configure::read('debug')) {\n                    $error['trace'] = $e->getTraceAsString();\n                }\n                $this->addError($error);\n            }\n        }","sourceCodeStart":83,"sourceCodeEnd":119,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/plugins/PassboltEe/Tags/src/Service/Metadata/MigrateAllV4TagsToV5Service.php#L83-L119","documentation":"MigrateAllV4TagsToV5Service::migrate converts V4 cleartext tags to V5 encrypted metadata. If the tag DTO already reports isV5(), the migration cannot proceed and an InternalErrorException naming the tag ID is thrown. This is an invariant check that prevents double-migration within a run.","triggerScenarios":"Re-running the v4-to-v5 tag migration while some tags were already converted in a previous (possibly partially completed) run; a tag persisted with metadata/metadata_key_id populated but processed again in the same batch.","commonSituations":"Migration job interrupted and restarted without filtering already-migrated tags; concurrent migration runs; stale batches cached between executions.","solutions":["Skip or filter tags already in V5 format before migrating (query tags where metadata IS NULL / v4 only).","Make migrate idempotent: catch this condition per-tag, log it, and continue with remaining tags.","Run the migration single-threaded to avoid concurrent double-processing.","Verify tag state in the DB (metadata_key_id set) before scheduling a rerun."],"exampleFix":"// before\nif ($dto->isV5()) { throw new InternalErrorException(...); }\n// after\nif ($dto->isV5()) { $this->skipped++; return; } // idempotent skip","handlingStrategy":"try-catch","validationCode":"$v4Tags = $tagsTable->find()\n    ->where(function ($exp) { return $exp->isNull('metadata'); })\n    ->all();","typeGuard":"function needsMigration(array $tag): bool {\n    return empty($tag['metadata']) || empty($tag['metadata_key_id']);\n}","tryCatchPattern":"try {\n    $service->migrate($uac, $batch);\n} catch (InternalErrorException $e) {\n    if (str_contains($e->getMessage(), 'is already V5')) {\n        $this->log('Skipping already-migrated tag; rerun with V4-only filter');\n    } else { throw $e; }\n}","preventionTips":["Filter V4-only tags in the migration query","Run migration jobs once, serialized","Make per-tag migration idempotent (skip instead of throw)","Track migration progress persistently between restarts"],"tags":["migration","v5","tags"],"backgroundTag":"invalid-state-transition","analyzedSha":"31c1bbc10f32808a607fa9bd81891e898779c0bc","analyzedAt":"2026-09-17T00:04:38.960Z","contentChangedAt":"2026-09-17T00:04:38.960Z","schemaVersion":2},"datasetVersion":"2026-09-21T04:17:39.646Z"}