{"record":{"id":"0f64e069ff55f4d0","repo":"passbolt/passbolt_api","slug":"can-not-upgrade-some-tables-are-missing","errorCode":null,"errorMessage":"Can not upgrade. Some tables are missing.","messagePattern":"Can not upgrade\\. Some tables are missing\\.","errorType":"exception","errorClass":"Exception","httpStatus":null,"severity":"error","filePath":"config/Migrations/20170830064410_V162InitialMigration.php","lineNumber":49,"sourceCode":"        $databaseName = $options['database'] ?? $options['name'];\n\n        // Check if v1 tables are present\n        $tables = [\n            'authentication_tokens', 'comments', 'controller_logs', 'email_queue',\n            'favorites', 'file_storage', 'gpgkeys', 'groups', 'groups_users', 'permissions',\n            'permissions_types', 'profiles', 'resources', 'roles', 'schema_migrations',\n            'secrets', 'user_agents', 'users',\n        ];\n        $tableCount = 0;\n        foreach ($tables as $table) {\n            $exists = $this->hasTable($table);\n            if ($exists) {\n                $tableCount++;\n            }\n        }\n        // If this is an upgrade from v1\n        if ($tableCount > 0 && $tableCount < sizeof($tables)) {\n            throw new Exception('Can not upgrade. Some tables are missing.');\n        }\n\n        // If this is an upgrade from v1\n        if ($tableCount > 0) {\n            // Check the latest 1.x migration is done\n            $latestMigrationName = 'Migration_1_6_1';\n            $schemaMigrationResult = $this->query(\"SELECT * FROM schema_migrations WHERE class='$latestMigrationName'\");\n            $schemaMigrationRows = $schemaMigrationResult->fetchAll();\n            if (!count($schemaMigrationRows)) {\n                throw new Exception('Can not upgrade. Please upgrade to the latest 1.x version first and retry. See https://help.passbolt.com/hosting/update.');\n            }\n        }\n\n        // Reset the collation just in case\n        if ($this->getAdapter()->getAdapterType() !== \"pgsql\") {\n            $this->execute('ALTER DATABASE `' . $databaseName . '` COLLATE utf8mb4_unicode_ci');\n       }\n","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/passbolt/passbolt_api/blob/31c1bbc10f32808a607fa9bd81891e898779c0bc/config/Migrations/20170830064410_V162InitialMigration.php#L31-L67","documentation":"Thrown by Gnupg::setDecryptKey after gnupg_adddecryptkey() fails for the just-imported armored key. It means GnuPG refused to register the key as a decryption key — typically because the passphrase is wrong or the key has no usable secret key. The underlying gnupg exception message is appended to the message.","triggerScenarios":"Calling setDecryptKey($armoredKey, $passphrase) where importKeyIntoKeyring succeeds but adddecryptkey throws: wrong passphrase, key without a secret/private part, or corrupted/unusable key material.","commonSituations":"Server key passphrase changed in config but not in passbolt; importing only the public half of a pair; passphrase containing special characters mishandled by env/config; key expired or revoked.","solutions":["Verify the passphrase configured for the server key is correct (test manually with gpg).","Ensure the armored key passed is the PRIVATE key (contains 'PRIVATE KEY BLOCK').","Re-import/re-generate the server key and update config (passbolt.serverGpg.keyId / fingerprint).","Inspect the appended gnupg exception message for the precise gnupg error code.","Check gnupg keyring permissions (GNUPGHOME writable by web server user) and key expiry."],"exampleFix":"// before\n$gpg->setDecryptKey($publicKey, $passphrase); // public key, no secret part\n// after\n$gpg->setDecryptKey($privateKey, $correctPassphrase);","handlingStrategy":"validation","validationCode":"if (strpos($armoredKey, 'BEGIN PGP PRIVATE KEY BLOCK') === false) {\n    throw new InvalidArgumentException('setDecryptKey requires a private key');\n}\n","typeGuard":"function isArmoredPrivateKey(string $s): bool {\n    return is_string($s) && strpos($s, 'BEGIN PGP PRIVATE KEY BLOCK') !== false;\n}\n","tryCatchPattern":"try {\n    $gpg->setDecryptKey($key, $pass);\n} catch (\\Cake\\Core\\Exception\\Exception $e) {\n    $this->log('decrypt key rejected: ' . $e->getMessage());\n    throw new ServerKeyConfigurationException(previous: $e);\n}\n","preventionTips":["Store the private server key and passphrase together in config and validate at boot (health check that setDecryptKey succeeds).","Test the passphrase with the gpg CLI before deploying config changes.","Never pass public keys to setDecryptKey.","Rotate keys with a documented procedure updating fingerprint + passphrase together."],"tags":["openpgp","gnupg","decryption","passphrase"],"backgroundTag":"invalid-argument-value","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"}