From 98d29aa99dafb60a69bfaac0cf0bde8200f623da Mon Sep 17 00:00:00 2001 From: Levi van Noort <73097785+levivannoort@users.noreply.github.com> Date: Wed, 12 Aug 2026 06:40:34 +0200 Subject: [PATCH] fix(destination): skip project variables with unusable keys Project variables are written straight into the variables collection here, which bypasses the API's validation of the key. The API only accepts keys that are valid environment variable names, since that is what they become at build and runtime, so an import can still store a key that fails at build time and cannot be fixed through the API afterwards. A source project can hold such a key when it was stored before the rule existed. Check the key before creating the document and skip the resource with a reason, matching how a duplicate key is already reported, so one bad key does not abort the import. Server-side rule: appwrite/appwrite#13181 Co-Authored-By: Claude Opus 5 (1M context) --- src/Migration/Destinations/Appwrite.php | 25 +++++++++++++++++++++++-- 1 file changed, 23 insertions(+), 2 deletions(-) diff --git a/src/Migration/Destinations/Appwrite.php b/src/Migration/Destinations/Appwrite.php index 0e20548e..a8eb59d0 100644 --- a/src/Migration/Destinations/Appwrite.php +++ b/src/Migration/Destinations/Appwrite.php @@ -89,6 +89,14 @@ class Appwrite extends Destination private const VECTORSDB_EMBEDDINGS_KEY = 'embeddings'; + /** + * Variable keys become environment variable names at build and runtime, so + * the API only accepts C-style identifiers. Variables are written straight + * to the collection here, which skips that check, and a source project can + * still hold a key stored before the rule existed. + */ + private const VARIABLE_KEY_PATTERN = '/^[A-Za-z_]\w*$/D'; + /** A database is provisioning while its resources transfer, ready once the run completes, or failed if creation errored. */ private const DATABASE_STATUS_PROVISIONING = 'provisioning'; private const DATABASE_STATUS_READY = 'ready'; @@ -3344,9 +3352,23 @@ public function importDomainsResource(Resource $resource): Resource protected function createProjectVariable(ProjectVariable $resource): bool { + $key = $resource->getKey(); + + if ( + \strlen($key) > UtopiaDatabase::LENGTH_KEY + || \preg_match(self::VARIABLE_KEY_PATTERN, $key) !== 1 + ) { + $resource->setStatus( + Resource::STATUS_SKIPPED, + 'Project variable key is not a valid environment variable name' + ); + + return false; + } + $existing = $this->dbForProject->findOne('variables', [ Query::equal('resourceType', ['project']), - Query::equal('key', [$resource->getKey()]), + Query::equal('key', [$key]), ]); if ($existing !== false && !$existing->isEmpty()) { @@ -3357,7 +3379,6 @@ protected function createProjectVariable(ProjectVariable $resource): bool $createdAt = $this->normalizeDateTime($resource->getCreatedAt()); $updatedAt = $this->normalizeDateTime($resource->getUpdatedAt(), $createdAt); $variableId = ID::unique(); - $key = $resource->getKey(); try { $this->dbForProject->createDocument('variables', new UtopiaDocument([