From 4703e4ba1465d78dbc7228f9ab66ef64111fecc4 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Mon, 4 May 2026 05:05:00 +0100 Subject: [PATCH 01/17] Gate Sentry logging in migrations worker by exception type The outer catch in the migrations worker now only calls logError when the caught Throwable is not a MigrationException. User-facing setup errors (invalid source type, missing project, etc.) are thrown as MigrationException with appropriate codes and stay in the migration report only. Removed the foreach loop that re-published collected errors to Sentry; with the library-side fix in utopia-php/migration, items in $source->getErrors() / $destination->getErrors() are by construction user errors that don't need Sentry routing. Hoisted setAttribute('errors', sanitizeErrors(...)) into finally so the migration document always reflects the consolidated error list, including on bug paths. --- src/Appwrite/Platform/Workers/Migrations.php | 47 +++++++++----------- 1 file changed, 20 insertions(+), 27 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index 69f72b8e27..63956bc90d 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -196,13 +196,13 @@ class Migrations extends Action $projectDB = null; $useAppwriteApiSource = false; if ($source === SourceAppwrite::getName() && empty($credentials['projectId'])) { - throw new \Exception('Source projectId is required for Appwrite migrations'); + throw new MigrationException('', '', message: 'Source projectId is required for Appwrite migrations', code: MigrationException::CODE_VALIDATION); } if (! empty($credentials['projectId'])) { $this->sourceProject = $this->dbForPlatform->getDocument('projects', $credentials['projectId']); if ($this->sourceProject->isEmpty()) { - throw new \Exception('Source project not found for provided projectId'); + throw new MigrationException('', '', message: 'Source project not found for provided projectId', code: MigrationException::CODE_NOT_FOUND); } $sourceRegion = $this->sourceProject->getAttribute('region', 'default'); @@ -265,7 +265,7 @@ class Migrations extends Action $this->deviceForMigrations, $this->dbForProject, ), - default => throw new \Exception('Invalid source type'), + default => throw new MigrationException('', '', message: 'Invalid source type', code: MigrationException::CODE_VALIDATION), }; $resources = $migration->getAttribute('resources', []); @@ -310,7 +310,7 @@ class Migrations extends Action $options['filename'], $options['columns'] ?? [], ), - default => throw new \Exception('Invalid destination type'), + default => throw new MigrationException('', '', message: 'Invalid destination type', code: MigrationException::CODE_VALIDATION), }; } @@ -521,7 +521,6 @@ class Migrations extends Action if (!empty($sourceErrors) || ! empty($destinationErrors)) { $migration->setAttribute('status', 'failed'); $migration->setAttribute('stage', 'finished'); - $migration->setAttribute('errors', $this->sanitizeErrors($sourceErrors, $destinationErrors)); return; } @@ -536,35 +535,29 @@ class Migrations extends Action $migration->setAttribute('status', 'failed'); $migration->setAttribute('stage', 'finished'); - call_user_func($this->logError, $th, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [ - 'migrationId' => $migration->getId(), - 'source' => $migration->getAttribute('source') ?? '', - 'destination' => $migration->getAttribute('destination') ?? '', - ]); - + // User-facing failures (validation, not found, conflict) are routed through + // MigrationException and stay in the migration report only. Anything else is + // a bug or infra failure and goes to Sentry with the full trace. + if (!$th instanceof MigrationException) { + call_user_func($this->logError, $th, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [ + 'migrationId' => $migration->getId(), + 'source' => $migration->getAttribute('source') ?? '', + 'destination' => $migration->getAttribute('destination') ?? '', + ]); + } } finally { try { + // Persist the consolidated error list regardless of which code path fired. + $migration->setAttribute('errors', $this->sanitizeErrors( + $source?->getErrors() ?? [], + $destination?->getErrors() ?? [], + )); + $this->updateMigrationDocument($migration, $project, $queueForRealtime); if ($migration->getAttribute('status', '') === 'failed') { Console::error('Migration(' . $migration->getSequence() . ':' . $migration->getId() . ') failed, Project(' . $this->project->getSequence() . ':' . $this->project->getId() . ')'); - $sourceErrors = $source?->getErrors() ?? []; - $destinationErrors = $destination?->getErrors() ?? []; - - foreach ([...$sourceErrors, ...$destinationErrors] as $error) { - /** @var MigrationException $error */ - if ($error->getCode() === 0 || $error->getCode() >= 500) { - ($this->logError)($error, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [ - 'migrationId' => $migration->getId(), - 'source' => $migration->getAttribute('source') ?? '', - 'destination' => $migration->getAttribute('destination') ?? '', - 'resourceName' => $error->getResourceName(), - 'resourceGroup' => $error->getResourceGroup(), - ]); - } - } - $source?->error(); $destination?->error(); } From c2e4bc2ae3901d040dd2d942ed74ac082e0f10f2 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Mon, 4 May 2026 07:27:27 +0100 Subject: [PATCH 02/17] Simplify sanitizeErrors now that jsonSerialize doesn't emit trace With Migration\Exception::jsonSerialize() no longer including the stack trace, sanitizeErrors no longer needs to decode/strip/re-encode each entry. Reduce it to a single json_encode pass. --- src/Appwrite/Platform/Workers/Migrations.php | 20 +++++--------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index 63956bc90d..03578b4ddc 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -857,7 +857,7 @@ class Migrations extends Action } /** - * Sanitize migration errors, removing sensitive information like stack traces + * Encode migration errors as JSON strings for storage on the migration document. * * @param array $sourceErrors * @param array $destinationErrors @@ -867,20 +867,10 @@ class Migrations extends Action array $sourceErrors, array $destinationErrors, ): array { - $errors = []; - foreach ([...$sourceErrors, ...$destinationErrors] as $error) { - $encoded = \json_decode(\json_encode($error), true); - if (\is_array($encoded)) { - if (isset($encoded['trace'])) { - unset($encoded['trace']); - } - $errors[] = \json_encode($encoded); - } else { - $errors[] = \json_encode($error); - } - } - - return $errors; + return \array_map( + fn ($error) => \json_encode($error), + [...$sourceErrors, ...$destinationErrors], + ); } private function processMigrationResourceStats(array $resources, Context $usage, Document $projectDocument, UsagePublisher $publisherForUsage, string $source, Authorization $authorization, ?string $resourceId) From c849e652b3d454e78b830a8b69eded42fb467ed3 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Mon, 4 May 2026 08:36:38 +0100 Subject: [PATCH 03/17] Pin utopia-php/migration to fix-migration-sentry-leak branch for testing Wires the worker against the matching utopia-php/migration branch so end-to-end testing of the Sentry-routing fix can run against both sides of the change set. Run `composer update utopia-php/migration --with-all-dependencies` after pulling to refresh composer.lock. Revert before merging. --- composer.json | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 683da6f21b..163e9bf839 100644 --- a/composer.json +++ b/composer.json @@ -74,7 +74,7 @@ "utopia-php/locale": "0.8.*", "utopia-php/logger": "0.6.*", "utopia-php/messaging": "0.22.*", - "utopia-php/migration": "1.9.*", + "utopia-php/migration": "dev-fix-migration-sentry-leak as 1.9.999", "utopia-php/platform": "0.13.*", "utopia-php/pools": "1.*", "utopia-php/span": "1.1.*", @@ -118,5 +118,11 @@ "php-http/discovery": true, "tbachert/spi": true } - } + }, + "repositories": [ + { + "type": "vcs", + "url": "https://github.com/utopia-php/migration" + } + ] } From 581fdb26dd0d399423fd514f28bf4ea123fb9233 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Mon, 4 May 2026 08:57:56 +0100 Subject: [PATCH 04/17] Update composer.lock for utopia-php/migration branch pin --- composer.lock | 76 +++++++++++++++++++++++++++++++++++---------------- 1 file changed, 52 insertions(+), 24 deletions(-) diff --git a/composer.lock b/composer.lock index 3edbc39614..7f38aea1cf 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "4bee36b21a57e754d2b3417e72dc9599", + "content-hash": "0e8cd1a2446dfb54015d25fa130d081a", "packages": [ { "name": "adhocore/jwt", @@ -2708,16 +2708,16 @@ }, { "name": "symfony/http-client", - "version": "v7.4.8", + "version": "v7.4.9", "source": { "type": "git", "url": "https://github.com/symfony/http-client.git", - "reference": "01933e626c3de76bea1e22641e205e78f6a34342" + "reference": "7e941c6abf4e3bf7dca160bf0e11ef36a9f832f6" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client/zipball/01933e626c3de76bea1e22641e205e78f6a34342", - "reference": "01933e626c3de76bea1e22641e205e78f6a34342", + "url": "https://api.github.com/repos/symfony/http-client/zipball/7e941c6abf4e3bf7dca160bf0e11ef36a9f832f6", + "reference": "7e941c6abf4e3bf7dca160bf0e11ef36a9f832f6", "shasum": "" }, "require": { @@ -2785,7 +2785,7 @@ "http" ], "support": { - "source": "https://github.com/symfony/http-client/tree/v7.4.8" + "source": "https://github.com/symfony/http-client/tree/v7.4.9" }, "funding": [ { @@ -2805,7 +2805,7 @@ "type": "tidelift" } ], - "time": "2026-03-30T12:55:43+00:00" + "time": "2026-04-29T13:25:15+00:00" }, { "name": "symfony/http-client-contracts", @@ -3850,22 +3850,23 @@ }, { "name": "utopia-php/database", - "version": "5.4.1", + "version": "5.6.0", "source": { "type": "git", "url": "https://github.com/utopia-php/database.git", - "reference": "688d9422b5ff42ac2ecc29397d94891cfd772e93" + "reference": "609ebcd64be1ec6fab00c5f46fce54acb0031b3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/database/zipball/688d9422b5ff42ac2ecc29397d94891cfd772e93", - "reference": "688d9422b5ff42ac2ecc29397d94891cfd772e93", + "url": "https://api.github.com/repos/utopia-php/database/zipball/609ebcd64be1ec6fab00c5f46fce54acb0031b3c", + "reference": "609ebcd64be1ec6fab00c5f46fce54acb0031b3c", "shasum": "" }, "require": { "ext-mbstring": "*", "ext-mongodb": "*", "ext-pdo": "*", + "ext-redis": "*", "php": ">=8.4", "utopia-php/cache": "1.*", "utopia-php/console": "0.1.*", @@ -3903,9 +3904,9 @@ ], "support": { "issues": "https://github.com/utopia-php/database/issues", - "source": "https://github.com/utopia-php/database/tree/5.4.1" + "source": "https://github.com/utopia-php/database/tree/5.6.0" }, - "time": "2026-04-29T07:32:59+00:00" + "time": "2026-05-01T01:28:07+00:00" }, { "name": "utopia-php/detector", @@ -4530,16 +4531,16 @@ }, { "name": "utopia-php/migration", - "version": "1.9.5", + "version": "dev-fix-migration-sentry-leak", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "952a4dfe232702f80e45c35129466a8d8cb4c599" + "reference": "276e7c25077a4dee670a806f715869f11367d932" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/952a4dfe232702f80e45c35129466a8d8cb4c599", - "reference": "952a4dfe232702f80e45c35129466a8d8cb4c599", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/276e7c25077a4dee670a806f715869f11367d932", + "reference": "276e7c25077a4dee670a806f715869f11367d932", "shasum": "" }, "require": { @@ -4565,7 +4566,25 @@ "Utopia\\Migration\\": "src/Migration" } }, - "notification-url": "https://packagist.org/downloads/", + "autoload-dev": { + "psr-4": { + "Utopia\\Tests\\": "tests/Migration" + } + }, + "scripts": { + "test": [ + "./vendor/bin/phpunit" + ], + "lint": [ + "./vendor/bin/pint --test" + ], + "format": [ + "./vendor/bin/pint" + ], + "check": [ + "./vendor/bin/phpstan analyse --level 3 src tests --memory-limit 2G" + ] + }, "license": [ "MIT" ], @@ -4578,10 +4597,10 @@ "utopia" ], "support": { - "issues": "https://github.com/utopia-php/migration/issues", - "source": "https://github.com/utopia-php/migration/tree/1.9.5" + "source": "https://github.com/utopia-php/migration/tree/fix-migration-sentry-leak", + "issues": "https://github.com/utopia-php/migration/issues" }, - "time": "2026-04-29T11:19:13+00:00" + "time": "2026-05-04T07:20:31+00:00" }, { "name": "utopia-php/mongo", @@ -8442,9 +8461,18 @@ "time": "2024-11-07T12:36:22+00:00" } ], - "aliases": [], + "aliases": [ + { + "package": "utopia-php/migration", + "version": "dev-fix-migration-sentry-leak", + "alias": "1.9.999", + "alias_normalized": "1.9.999.0" + } + ], "minimum-stability": "dev", - "stability-flags": [], + "stability-flags": { + "utopia-php/migration": 20 + }, "prefer-stable": true, "prefer-lowest": false, "platform": { @@ -8465,5 +8493,5 @@ "platform-dev": { "ext-fileinfo": "*" }, - "plugin-api-version": "2.6.0" + "plugin-api-version": "2.9.0" } From 07d60bb36d25e9a8e307d29c56c10f8071630ad0 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Mon, 4 May 2026 10:23:40 +0100 Subject: [PATCH 05/17] Capture bubbled exception in migration error report The catch block recorded status='failed' but didn't surface the bubbling exception's message on the migration document. Setup-time failures (e.g. "Source project not found for provided projectId") left the user looking at status='failed' with errors=[]. Capture the throwable in the catch and include it in the consolidated errors list when finally serializes to the migration document. --- src/Appwrite/Platform/Workers/Migrations.php | 26 ++++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index 03578b4ddc..fa3900881d 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -428,6 +428,7 @@ class Migrations extends Action $transfer = $source = $destination = null; $aggregatedResources = []; + $caughtError = null; $host = System::getEnv('_APP_MIGRATION_HOST'); if (empty($host)) { @@ -535,6 +536,11 @@ class Migrations extends Action $migration->setAttribute('status', 'failed'); $migration->setAttribute('stage', 'finished'); + // Remember the bubbled exception so the finally block can include it in the + // migration's errors attribute — otherwise setup-time failures (e.g. invalid + // credentials) would leave the user looking at status='failed' with no message. + $caughtError = $th; + // User-facing failures (validation, not found, conflict) are routed through // MigrationException and stay in the migration report only. Anything else is // a bug or infra failure and goes to Sentry with the full trace. @@ -547,10 +553,26 @@ class Migrations extends Action } } finally { try { + $sourceErrors = $source?->getErrors() ?? []; + $destinationErrors = $destination?->getErrors() ?? []; + + if ($caughtError !== null) { + $bubbled = $caughtError instanceof MigrationException + ? $caughtError + : new MigrationException( + resourceName: '', + resourceGroup: '', + message: $caughtError->getMessage(), + code: $caughtError->getCode(), + previous: $caughtError, + ); + $destinationErrors[] = $bubbled; + } + // Persist the consolidated error list regardless of which code path fired. $migration->setAttribute('errors', $this->sanitizeErrors( - $source?->getErrors() ?? [], - $destination?->getErrors() ?? [], + $sourceErrors, + $destinationErrors, )); $this->updateMigrationDocument($migration, $project, $queueForRealtime); From 3bb68ef4559ccf0880b9221d3b2f5d61d648ff2c Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Mon, 4 May 2026 11:03:51 +0100 Subject: [PATCH 06/17] Trim verbose comments in migrations worker outer catch --- src/Appwrite/Platform/Workers/Migrations.php | 8 +------- 1 file changed, 1 insertion(+), 7 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index fa3900881d..8025c20677 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -536,14 +536,9 @@ class Migrations extends Action $migration->setAttribute('status', 'failed'); $migration->setAttribute('stage', 'finished'); - // Remember the bubbled exception so the finally block can include it in the - // migration's errors attribute — otherwise setup-time failures (e.g. invalid - // credentials) would leave the user looking at status='failed' with no message. $caughtError = $th; - // User-facing failures (validation, not found, conflict) are routed through - // MigrationException and stay in the migration report only. Anything else is - // a bug or infra failure and goes to Sentry with the full trace. + // MigrationException is reserved for user-facing failures and stays in the migration report only. if (!$th instanceof MigrationException) { call_user_func($this->logError, $th, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [ 'migrationId' => $migration->getId(), @@ -569,7 +564,6 @@ class Migrations extends Action $destinationErrors[] = $bubbled; } - // Persist the consolidated error list regardless of which code path fired. $migration->setAttribute('errors', $this->sanitizeErrors( $sourceErrors, $destinationErrors, From 561b482e5439d4cce20b1c8735794453d7676e44 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Mon, 4 May 2026 11:20:00 +0100 Subject: [PATCH 07/17] Restore original sanitizeErrors decode/strip/encode flow --- src/Appwrite/Platform/Workers/Migrations.php | 20 +++++++++++++++----- 1 file changed, 15 insertions(+), 5 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index 8025c20677..f5cc57a6c2 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -873,7 +873,7 @@ class Migrations extends Action } /** - * Encode migration errors as JSON strings for storage on the migration document. + * Sanitize migration errors, removing sensitive information like stack traces * * @param array $sourceErrors * @param array $destinationErrors @@ -883,10 +883,20 @@ class Migrations extends Action array $sourceErrors, array $destinationErrors, ): array { - return \array_map( - fn ($error) => \json_encode($error), - [...$sourceErrors, ...$destinationErrors], - ); + $errors = []; + foreach ([...$sourceErrors, ...$destinationErrors] as $error) { + $encoded = \json_decode(\json_encode($error), true); + if (\is_array($encoded)) { + if (isset($encoded['trace'])) { + unset($encoded['trace']); + } + $errors[] = \json_encode($encoded); + } else { + $errors[] = \json_encode($error); + } + } + + return $errors; } private function processMigrationResourceStats(array $resources, Context $usage, Document $projectDocument, UsagePublisher $publisherForUsage, string $source, Authorization $authorization, ?string $resourceId) From 803f646239d8b39b0e411d75bbbc38cc60b11c0f Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Tue, 5 May 2026 17:19:40 +0100 Subject: [PATCH 08/17] Update composer.lock for utopia-php/migration da8205e --- composer.lock | 32 ++++++++++++++++---------------- 1 file changed, 16 insertions(+), 16 deletions(-) diff --git a/composer.lock b/composer.lock index 7f38aea1cf..43dd28b150 100644 --- a/composer.lock +++ b/composer.lock @@ -4535,12 +4535,12 @@ "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "276e7c25077a4dee670a806f715869f11367d932" + "reference": "da8205e8f3e927b2b860dddc2efca0e408171116" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/276e7c25077a4dee670a806f715869f11367d932", - "reference": "276e7c25077a4dee670a806f715869f11367d932", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/da8205e8f3e927b2b860dddc2efca0e408171116", + "reference": "da8205e8f3e927b2b860dddc2efca0e408171116", "shasum": "" }, "require": { @@ -4600,7 +4600,7 @@ "source": "https://github.com/utopia-php/migration/tree/fix-migration-sentry-leak", "issues": "https://github.com/utopia-php/migration/issues" }, - "time": "2026-05-04T07:20:31+00:00" + "time": "2026-05-05T15:50:58+00:00" }, { "name": "utopia-php/mongo", @@ -5039,16 +5039,16 @@ }, { "name": "utopia-php/storage", - "version": "2.0.1", + "version": "2.0.2", "source": { "type": "git", "url": "https://github.com/utopia-php/storage.git", - "reference": "8a2e3a86fd01aaed675884146665308c2122264e" + "reference": "64e132a3768e22243eda36fe4262da22fd204f3c" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/storage/zipball/8a2e3a86fd01aaed675884146665308c2122264e", - "reference": "8a2e3a86fd01aaed675884146665308c2122264e", + "url": "https://api.github.com/repos/utopia-php/storage/zipball/64e132a3768e22243eda36fe4262da22fd204f3c", + "reference": "64e132a3768e22243eda36fe4262da22fd204f3c", "shasum": "" }, "require": { @@ -5085,22 +5085,22 @@ ], "support": { "issues": "https://github.com/utopia-php/storage/issues", - "source": "https://github.com/utopia-php/storage/tree/2.0.1" + "source": "https://github.com/utopia-php/storage/tree/2.0.2" }, - "time": "2026-04-29T09:05:48+00:00" + "time": "2026-05-01T15:06:16+00:00" }, { "name": "utopia-php/system", - "version": "0.10.1", + "version": "0.10.2", "source": { "type": "git", "url": "https://github.com/utopia-php/system.git", - "reference": "7c1669533bb9c285de19191270c8c1439161a78a" + "reference": "04229a822b147c1abaf1a92fb42c2d7aad4625df" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/system/zipball/7c1669533bb9c285de19191270c8c1439161a78a", - "reference": "7c1669533bb9c285de19191270c8c1439161a78a", + "url": "https://api.github.com/repos/utopia-php/system/zipball/04229a822b147c1abaf1a92fb42c2d7aad4625df", + "reference": "04229a822b147c1abaf1a92fb42c2d7aad4625df", "shasum": "" }, "require": { @@ -5141,9 +5141,9 @@ ], "support": { "issues": "https://github.com/utopia-php/system/issues", - "source": "https://github.com/utopia-php/system/tree/0.10.1" + "source": "https://github.com/utopia-php/system/tree/0.10.2" }, - "time": "2026-03-15T21:07:41+00:00" + "time": "2026-05-05T14:33:41+00:00" }, { "name": "utopia-php/telemetry", From b094993f77b9f7036ff863cb5c8ff0b741dc9506 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Wed, 6 May 2026 12:34:50 +0100 Subject: [PATCH 09/17] spike: convert worker throws to AppwriteException + isPublishable gate --- app/config/errors.php | 20 ++++++++++++++++++ src/Appwrite/Extend/Exception.php | 4 ++++ src/Appwrite/Platform/Workers/Migrations.php | 22 ++++++++++++++------ 3 files changed, 40 insertions(+), 6 deletions(-) diff --git a/app/config/errors.php b/app/config/errors.php index fa112bcb6f..08cc497b3f 100644 --- a/app/config/errors.php +++ b/app/config/errors.php @@ -1236,6 +1236,26 @@ return [ 'description' => 'The specified database type is not supported for CSV import or export operations.', 'code' => 400, ], + Exception::MIGRATION_SOURCE_PROJECT_ID_REQUIRED => [ + 'name' => Exception::MIGRATION_SOURCE_PROJECT_ID_REQUIRED, + 'description' => 'A source projectId is required for Appwrite migrations. Provide it in the migration credentials.', + 'code' => 400, + ], + Exception::MIGRATION_SOURCE_PROJECT_NOT_FOUND => [ + 'name' => Exception::MIGRATION_SOURCE_PROJECT_NOT_FOUND, + 'description' => 'The source project for the provided projectId was not found. Verify the projectId and the API key has access to it.', + 'code' => 404, + ], + Exception::MIGRATION_SOURCE_TYPE_INVALID => [ + 'name' => Exception::MIGRATION_SOURCE_TYPE_INVALID, + 'description' => 'The migration source type is invalid. Use one of the supported source types.', + 'code' => 400, + ], + Exception::MIGRATION_DESTINATION_TYPE_INVALID => [ + 'name' => Exception::MIGRATION_DESTINATION_TYPE_INVALID, + 'description' => 'The migration destination type is invalid. Use one of the supported destination types.', + 'code' => 400, + ], /** Realtime */ Exception::REALTIME_MESSAGE_FORMAT_INVALID => [ diff --git a/src/Appwrite/Extend/Exception.php b/src/Appwrite/Extend/Exception.php index 6fc3e88635..54ee4069d1 100644 --- a/src/Appwrite/Extend/Exception.php +++ b/src/Appwrite/Extend/Exception.php @@ -346,6 +346,10 @@ class Exception extends \Exception public const string MIGRATION_IN_PROGRESS = 'migration_in_progress'; public const string MIGRATION_PROVIDER_ERROR = 'migration_provider_error'; public const string MIGRATION_DATABASE_TYPE_UNSUPPORTED = 'migration_database_type_unsupported'; + public const string MIGRATION_SOURCE_PROJECT_ID_REQUIRED = 'migration_source_project_id_required'; + public const string MIGRATION_SOURCE_PROJECT_NOT_FOUND = 'migration_source_project_not_found'; + public const string MIGRATION_SOURCE_TYPE_INVALID = 'migration_source_type_invalid'; + public const string MIGRATION_DESTINATION_TYPE_INVALID = 'migration_destination_type_invalid'; /** Realtime */ public const string REALTIME_MESSAGE_FORMAT_INVALID = 'realtime_message_format_invalid'; diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index f5cc57a6c2..f37571d2b0 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -196,13 +196,13 @@ class Migrations extends Action $projectDB = null; $useAppwriteApiSource = false; if ($source === SourceAppwrite::getName() && empty($credentials['projectId'])) { - throw new MigrationException('', '', message: 'Source projectId is required for Appwrite migrations', code: MigrationException::CODE_VALIDATION); + throw new Exception(Exception::MIGRATION_SOURCE_PROJECT_ID_REQUIRED); } if (! empty($credentials['projectId'])) { $this->sourceProject = $this->dbForPlatform->getDocument('projects', $credentials['projectId']); if ($this->sourceProject->isEmpty()) { - throw new MigrationException('', '', message: 'Source project not found for provided projectId', code: MigrationException::CODE_NOT_FOUND); + throw new Exception(Exception::MIGRATION_SOURCE_PROJECT_NOT_FOUND); } $sourceRegion = $this->sourceProject->getAttribute('region', 'default'); @@ -265,7 +265,7 @@ class Migrations extends Action $this->deviceForMigrations, $this->dbForProject, ), - default => throw new MigrationException('', '', message: 'Invalid source type', code: MigrationException::CODE_VALIDATION), + default => throw new Exception(Exception::MIGRATION_SOURCE_TYPE_INVALID), }; $resources = $migration->getAttribute('resources', []); @@ -310,7 +310,7 @@ class Migrations extends Action $options['filename'], $options['columns'] ?? [], ), - default => throw new MigrationException('', '', message: 'Invalid destination type', code: MigrationException::CODE_VALIDATION), + default => throw new Exception(Exception::MIGRATION_DESTINATION_TYPE_INVALID), }; } @@ -538,8 +538,18 @@ class Migrations extends Action $caughtError = $th; - // MigrationException is reserved for user-facing failures and stays in the migration report only. - if (!$th instanceof MigrationException) { + // Mirror general.php's HTTP-error pattern: typed AppwriteException uses its + // registry-driven isPublishable() flag; library-thrown Migration\Exception is + // always user-facing; anything else falls back to the code heuristic. + if ($th instanceof Exception) { + $publish = $th->isPublishable(); + } elseif ($th instanceof MigrationException) { + $publish = false; + } else { + $publish = $th->getCode() === 0 || $th->getCode() >= 500; + } + + if ($publish) { call_user_func($this->logError, $th, 'appwrite-worker', 'appwrite-queue-' . self::getName(), [ 'migrationId' => $migration->getId(), 'source' => $migration->getAttribute('source') ?? '', From 85d7c27a38c62bfd9e20efd9b3d06d65ddd16053 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Thu, 7 May 2026 03:50:08 +0100 Subject: [PATCH 10/17] spike: treat null exception code as publishable in fallback gate --- src/Appwrite/Platform/Workers/Migrations.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index 2af3752085..5588a77799 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -554,7 +554,7 @@ class Migrations extends Action } elseif ($th instanceof MigrationException) { $publish = false; } else { - $publish = $th->getCode() === 0 || $th->getCode() >= 500; + $publish = $th->getCode() === null || $th->getCode() === 0 || $th->getCode() >= 500; } if ($publish) { From 8e9fd3256615b0a38e6f808b058baa8c4446a1b6 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Thu, 7 May 2026 07:41:47 +0100 Subject: [PATCH 11/17] spike: simplify gate fallback to always publish on unknown exception types --- src/Appwrite/Platform/Workers/Migrations.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index 5588a77799..e4274b2a8c 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -554,7 +554,7 @@ class Migrations extends Action } elseif ($th instanceof MigrationException) { $publish = false; } else { - $publish = $th->getCode() === null || $th->getCode() === 0 || $th->getCode() >= 500; + $publish = true; } if ($publish) { From e7d338466083cc1e72a7d6b4b3f71b20dda15f19 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Thu, 7 May 2026 09:22:21 +0100 Subject: [PATCH 12/17] Pin utopia-php/migration to 1.10.* (release with Sentry-leak fix) --- composer.json | 2 +- composer.lock | 77 +++++++++++++++++++++++++-------------------------- 2 files changed, 39 insertions(+), 40 deletions(-) diff --git a/composer.json b/composer.json index 35961a37cf..e396f6ad10 100644 --- a/composer.json +++ b/composer.json @@ -74,7 +74,7 @@ "utopia-php/locale": "0.8.*", "utopia-php/logger": "0.6.*", "utopia-php/messaging": "0.22.*", - "utopia-php/migration": "dev-fix-migration-sentry-leak as 1.9.999", + "utopia-php/migration": "1.10.*", "utopia-php/platform": "0.13.*", "utopia-php/pools": "1.*", "utopia-php/span": "1.1.*", diff --git a/composer.lock b/composer.lock index ad0ad5b137..a1f8309c17 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "b6fc61ff6050d420307ba99ed2e59d46", + "content-hash": "d0e14ee465162e3b5e56d6cef9ddbb8d", "packages": [ { "name": "adhocore/jwt", @@ -2641,16 +2641,16 @@ }, { "name": "symfony/deprecation-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/deprecation-contracts.git", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62" + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/63afe740e99a13ba87ec199bb07bbdee937a5b62", - "reference": "63afe740e99a13ba87ec199bb07bbdee937a5b62", + "url": "https://api.github.com/repos/symfony/deprecation-contracts/zipball/50f59d1f3ca46d41ac911f97a78626b6756af35b", + "reference": "50f59d1f3ca46d41ac911f97a78626b6756af35b", "shasum": "" }, "require": { @@ -2663,7 +2663,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -2688,7 +2688,7 @@ "description": "A generic function and convention to trigger deprecation notices", "homepage": "https://symfony.com", "support": { - "source": "https://github.com/symfony/deprecation-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/deprecation-contracts/tree/v3.7.0" }, "funding": [ { @@ -2699,12 +2699,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2024-09-25T14:21:43+00:00" + "time": "2026-04-13T15:52:40+00:00" }, { "name": "symfony/http-client", @@ -2809,16 +2813,16 @@ }, { "name": "symfony/http-client-contracts", - "version": "v3.6.0", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/http-client-contracts.git", - "reference": "75d7043853a42837e68111812f4d964b01e5101c" + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/75d7043853a42837e68111812f4d964b01e5101c", - "reference": "75d7043853a42837e68111812f4d964b01e5101c", + "url": "https://api.github.com/repos/symfony/http-client-contracts/zipball/4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", + "reference": "4a2d00c37651c0bdc2b9e1c773487a8bf4edb12d", "shasum": "" }, "require": { @@ -2831,7 +2835,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -2867,7 +2871,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/http-client-contracts/tree/v3.6.0" + "source": "https://github.com/symfony/http-client-contracts/tree/v3.7.0" }, "funding": [ { @@ -2878,12 +2882,16 @@ "url": "https://github.com/fabpot", "type": "github" }, + { + "url": "https://github.com/nicolas-grekas", + "type": "github" + }, { "url": "https://tidelift.com/funding/github/packagist/symfony/symfony", "type": "tidelift" } ], - "time": "2025-04-29T11:18:49+00:00" + "time": "2026-03-06T13:17:50+00:00" }, { "name": "symfony/polyfill-mbstring", @@ -3212,16 +3220,16 @@ }, { "name": "symfony/service-contracts", - "version": "v3.6.1", + "version": "v3.7.0", "source": { "type": "git", "url": "https://github.com/symfony/service-contracts.git", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43" + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/symfony/service-contracts/zipball/45112560a3ba2d715666a509a0bc9521d10b6c43", - "reference": "45112560a3ba2d715666a509a0bc9521d10b6c43", + "url": "https://api.github.com/repos/symfony/service-contracts/zipball/d25d82433a80eba6aa0e6c24b61d7370d99e444a", + "reference": "d25d82433a80eba6aa0e6c24b61d7370d99e444a", "shasum": "" }, "require": { @@ -3239,7 +3247,7 @@ "name": "symfony/contracts" }, "branch-alias": { - "dev-main": "3.6-dev" + "dev-main": "3.7-dev" } }, "autoload": { @@ -3275,7 +3283,7 @@ "standards" ], "support": { - "source": "https://github.com/symfony/service-contracts/tree/v3.6.1" + "source": "https://github.com/symfony/service-contracts/tree/v3.7.0" }, "funding": [ { @@ -3295,7 +3303,7 @@ "type": "tidelift" } ], - "time": "2025-07-15T11:30:57+00:00" + "time": "2026-03-28T09:44:51+00:00" }, { "name": "tbachert/spi", @@ -4531,16 +4539,16 @@ }, { "name": "utopia-php/migration", - "version": "dev-fix-migration-sentry-leak", + "version": "1.10.1", "source": { "type": "git", "url": "https://github.com/utopia-php/migration.git", - "reference": "da8205e8f3e927b2b860dddc2efca0e408171116" + "reference": "759d6d61b327313cbeeeb4ea0c3e2459164b4827" }, "dist": { "type": "zip", - "url": "https://api.github.com/repos/utopia-php/migration/zipball/da8205e8f3e927b2b860dddc2efca0e408171116", - "reference": "da8205e8f3e927b2b860dddc2efca0e408171116", + "url": "https://api.github.com/repos/utopia-php/migration/zipball/759d6d61b327313cbeeeb4ea0c3e2459164b4827", + "reference": "759d6d61b327313cbeeeb4ea0c3e2459164b4827", "shasum": "" }, "require": { @@ -4597,10 +4605,10 @@ "utopia" ], "support": { - "source": "https://github.com/utopia-php/migration/tree/fix-migration-sentry-leak", + "source": "https://github.com/utopia-php/migration/tree/1.10.1", "issues": "https://github.com/utopia-php/migration/issues" }, - "time": "2026-05-05T15:50:58+00:00" + "time": "2026-05-07T07:23:57+00:00" }, { "name": "utopia-php/mongo", @@ -8461,18 +8469,9 @@ "time": "2024-11-07T12:36:22+00:00" } ], - "aliases": [ - { - "package": "utopia-php/migration", - "version": "dev-fix-migration-sentry-leak", - "alias": "1.9.999", - "alias_normalized": "1.9.999.0" - } - ], + "aliases": [], "minimum-stability": "dev", - "stability-flags": { - "utopia-php/migration": 20 - }, + "stability-flags": {}, "prefer-stable": true, "prefer-lowest": false, "platform": { From e4b51d0abbc4d1820b34e38bb067c60274c14885 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Thu, 7 May 2026 13:10:05 +0100 Subject: [PATCH 13/17] Drop custom utopia-php/migration VCS repo (now resolved via Packagist) --- composer.json | 8 +------- composer.lock | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/composer.json b/composer.json index e396f6ad10..4a60944351 100644 --- a/composer.json +++ b/composer.json @@ -118,11 +118,5 @@ "php-http/discovery": true, "tbachert/spi": true } - }, - "repositories": [ - { - "type": "vcs", - "url": "https://github.com/utopia-php/migration" - } - ] + } } diff --git a/composer.lock b/composer.lock index a1f8309c17..eb06192bb9 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "d0e14ee465162e3b5e56d6cef9ddbb8d", + "content-hash": "acd8a12e57d36a970effa84f5c8ccf50", "packages": [ { "name": "adhocore/jwt", From 409cebb26e4f50fb041b54a5f0552dc4a9277a89 Mon Sep 17 00:00:00 2001 From: Jake Barnby Date: Fri, 8 May 2026 00:12:20 +1200 Subject: [PATCH 14/17] Apply suggestion from @greptile-apps[bot] Co-authored-by: greptile-apps[bot] <165735046+greptile-apps[bot]@users.noreply.github.com> --- src/Appwrite/Platform/Workers/Migrations.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index c893d022b8..bf64285299 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -556,7 +556,7 @@ class Migrations extends Action } elseif ($th instanceof MigrationException) { $publish = false; } else { - $publish = true; + $publish = $th->getCode() === 0 || $th->getCode() >= 500; } if ($publish) { From 98b4e9b0639ced493044e7716ea35bd7b6be3f59 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Thu, 7 May 2026 13:30:21 +0100 Subject: [PATCH 15/17] Loosen utopia-php/migration constraint back to 1.* (matches 1.9.x baseline) --- composer.json | 2 +- composer.lock | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/composer.json b/composer.json index 4a60944351..9a84be6111 100644 --- a/composer.json +++ b/composer.json @@ -74,7 +74,7 @@ "utopia-php/locale": "0.8.*", "utopia-php/logger": "0.6.*", "utopia-php/messaging": "0.22.*", - "utopia-php/migration": "1.10.*", + "utopia-php/migration": "1.*", "utopia-php/platform": "0.13.*", "utopia-php/pools": "1.*", "utopia-php/span": "1.1.*", diff --git a/composer.lock b/composer.lock index eb06192bb9..d356362788 100644 --- a/composer.lock +++ b/composer.lock @@ -4,7 +4,7 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "acd8a12e57d36a970effa84f5c8ccf50", + "content-hash": "ec2ad489c60f0102f0dfab223b6d1fe4", "packages": [ { "name": "adhocore/jwt", From 81e57ea30e2a0b2884185a4716ac106034ad3fee Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Thu, 7 May 2026 13:36:49 +0100 Subject: [PATCH 16/17] Revert gate fallback to always-publish on unknown exception types --- src/Appwrite/Platform/Workers/Migrations.php | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index bf64285299..f77a1cede0 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -550,13 +550,13 @@ class Migrations extends Action // Mirror general.php's HTTP-error pattern: typed AppwriteException uses its // registry-driven isPublishable() flag; library-thrown Migration\Exception is - // always user-facing; anything else falls back to the code heuristic. + // always user-facing; anything else is unknown and surfaced to Sentry. if ($th instanceof Exception) { $publish = $th->isPublishable(); } elseif ($th instanceof MigrationException) { $publish = false; } else { - $publish = $th->getCode() === 0 || $th->getCode() >= 500; + $publish = true; } if ($publish) { From 3676af925c19ace8ae9ddb36d456ac05d6ea33b7 Mon Sep 17 00:00:00 2001 From: Prem Palanisamy Date: Thu, 7 May 2026 13:45:57 +0100 Subject: [PATCH 17/17] Sanitize bubbled message for non-typed exceptions on migration document --- src/Appwrite/Platform/Workers/Migrations.php | 21 +++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/src/Appwrite/Platform/Workers/Migrations.php b/src/Appwrite/Platform/Workers/Migrations.php index f77a1cede0..b6c295b3bb 100644 --- a/src/Appwrite/Platform/Workers/Migrations.php +++ b/src/Appwrite/Platform/Workers/Migrations.php @@ -572,15 +572,30 @@ class Migrations extends Action $destinationErrors = $destination?->getErrors() ?? []; if ($caughtError !== null) { - $bubbled = $caughtError instanceof MigrationException - ? $caughtError - : new MigrationException( + if ($caughtError instanceof MigrationException) { + // library-thrown, message constructed by us + $bubbled = $caughtError; + } elseif ($caughtError instanceof Exception) { + // typed AppwriteException — message comes from the curated registry + $bubbled = new MigrationException( resourceName: '', resourceGroup: '', message: $caughtError->getMessage(), code: $caughtError->getCode(), previous: $caughtError, ); + } else { + // unknown throwable — raw message may embed internal hostnames, + // DSNs, tokens, etc. Replace with a generic user-facing string; + // the original is preserved on `previous:` for Sentry. + $bubbled = new MigrationException( + resourceName: '', + resourceGroup: '', + message: 'Migration failed due to an unexpected error.', + code: $caughtError->getCode() ?: 500, + previous: $caughtError, + ); + } $destinationErrors[] = $bubbled; }