passbolt/passbolt_api · error · Exception

Can not upgrade. Some tables are missing.

Error message

Can not upgrade. Some tables are missing.

What it means

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.

Solutions

  1. Verify the passphrase configured for the server key is correct (test manually with gpg).
  2. Ensure the armored key passed is the PRIVATE key (contains 'PRIVATE KEY BLOCK').
  3. Re-import/re-generate the server key and update config (passbolt.serverGpg.keyId / fingerprint).
  4. Inspect the appended gnupg exception message for the precise gnupg error code.
  5. Check gnupg keyring permissions (GNUPGHOME writable by web server user) and key expiry.

Example fix

// before
$gpg->setDecryptKey($publicKey, $passphrase); // public key, no secret part
// after
$gpg->setDecryptKey($privateKey, $correctPassphrase);
Defensive patterns

Strategy: validation

Validate before calling

if (strpos($armoredKey, 'BEGIN PGP PRIVATE KEY BLOCK') === false) {
    throw new InvalidArgumentException('setDecryptKey requires a private key');
}

Type guard

function isArmoredPrivateKey(string $s): bool {
    return is_string($s) && strpos($s, 'BEGIN PGP PRIVATE KEY BLOCK') !== false;
}

Try / catch

try {
    $gpg->setDecryptKey($key, $pass);
} catch (\Cake\Core\Exception\Exception $e) {
    $this->log('decrypt key rejected: ' . $e->getMessage());
    throw new ServerKeyConfigurationException(previous: $e);
}

Prevention

When it happens

Trigger: Calling setDecryptKey($armoredKey, $passphrase) where importKeyIntoKeyring succeeds but adddecryptkey throws: wrong passphrase, key without a secret/private part, or corrupted/unusable key material.

Common situations: 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.

Understand the failure class

Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.

Related errors


AI-assisted analysis of passbolt/passbolt_api@31c1bbc10f (2026-09-17). Data as JSON: /api/errors/0f64e069ff55f4d0. Report an issue: GitHub.

Appendix: source

Thrown at config/Migrations/20170830064410_V162InitialMigration.php:49

        $databaseName = $options['database'] ?? $options['name'];

        // Check if v1 tables are present
        $tables = [
            'authentication_tokens', 'comments', 'controller_logs', 'email_queue',
            'favorites', 'file_storage', 'gpgkeys', 'groups', 'groups_users', 'permissions',
            'permissions_types', 'profiles', 'resources', 'roles', 'schema_migrations',
            'secrets', 'user_agents', 'users',
        ];
        $tableCount = 0;
        foreach ($tables as $table) {
            $exists = $this->hasTable($table);
            if ($exists) {
                $tableCount++;
            }
        }
        // If this is an upgrade from v1
        if ($tableCount > 0 && $tableCount < sizeof($tables)) {
            throw new Exception('Can not upgrade. Some tables are missing.');
        }

        // If this is an upgrade from v1
        if ($tableCount > 0) {
            // Check the latest 1.x migration is done
            $latestMigrationName = 'Migration_1_6_1';
            $schemaMigrationResult = $this->query("SELECT * FROM schema_migrations WHERE class='$latestMigrationName'");
            $schemaMigrationRows = $schemaMigrationResult->fetchAll();
            if (!count($schemaMigrationRows)) {
                throw new Exception('Can not upgrade. Please upgrade to the latest 1.x version first and retry. See https://help.passbolt.com/hosting/update.');
            }
        }

        // Reset the collation just in case
        if ($this->getAdapter()->getAdapterType() !== "pgsql") {
            $this->execute('ALTER DATABASE `' . $databaseName . '` COLLATE utf8mb4_unicode_ci');
       }

View on GitHub (pinned to 31c1bbc10f)