Eugeny/tabby · error · Error

Vault not configured

Error message

Vault not configured

What it means

Thrown by `ConfigService.maybeEncryptConfig` when the user's config has `encrypted: true` but `VaultService.load()` returns null, meaning there is no stored vault to encrypt into. The vault must be set up (a StoredVault created and unlocked) before config encryption can run. This prevents silently writing plaintext or losing the encrypted-config invariant.

Source

Thrown at tabby-core/src/services/config.service.ts:537

        }
        delete decryptedVault.config.vault
        delete decryptedVault.config.encrypted
        delete decryptedVault.config.configSync
        return {
            ...decryptedVault.config,
            vault: store.vault,
            encrypted: store.encrypted,
            configSync: store.configSync,
        }
    }

    private async maybeEncryptConfig (store) {
        if (!store.encrypted) {
            return store
        }
        const vault = await this.vault.load()
        if (!vault) {
            throw new Error('Vault not configured')
        }
        vault.config = { ...store }
        delete vault.config.vault
        delete vault.config.encrypted
        delete vault.config.configSync
        return {
            vault: await this.vault.encrypt(vault),
            encrypted: true,
            configSync: store.configSync,
        }
    }
}

View on GitHub (pinned to 14e2d60b9b)

Solutions

  1. Set up the vault before enabling encryption: call `vault.setEnabled(true, passphrase)` so a StoredVault is created, then set `store.encrypted = true`.
  2. If the `encrypted` flag is stale, reset it: set `store.encrypted = false` (and `store.vault = null`) so config is stored in plaintext until the user explicitly enables the vault.
  3. Prompt the user for a master passphrase via the unlock modal flow before triggering `maybeEncryptConfig` when encryption is requested.
  4. Validate the invariant early: in config load, if `store.encrypted && !vault.store`, either trigger vault setup or downgrade to plaintext with a notification.

Example fix

// before
private async maybeEncryptConfig (store) {
    if (!store.encrypted) return store
    const vault = await this.vault.load()
    if (!vault) throw new Error('Vault not configured')
    ...
}

// after - ensure vault exists before encryption is allowed
if (!store.encrypted) return store
if (!this.vault.isEnabled() || !(await this.vault.load())) {
    await this.vault.setEnabled(true, await promptPassphrase())
}
const vault = await this.vault.load()
Defensive patterns

Strategy: validation

Validate before calling

async function ensureVaultBeforeEncrypt (vault: VaultService, store: any): Promise<void> {
    if (!store.encrypted) return
    if (!vault.isEnabled() || !(await vault.load())) {
        await vault.setEnabled(true, await promptForMasterPassphrase())
    }
    if (!(await vault.load())) {
        throw new Error('Vault not configured: refusing to mark config encrypted without an unlocked vault')
    }
}

Type guard

function isStoredVaultReady (v: unknown): v is { version: number; contents: string; keySalt: string; iv: string } {
    return typeof v === 'object' && v !== null &&
        typeof (v as any).version === 'number' &&
        typeof (v as any).contents === 'string' &&
        typeof (v as any).keySalt === 'string' &&
        typeof (v as any).iv === 'string'
}

Try / catch

try {
    await config.save()
} catch (e) {
    if (e instanceof Error && e.message === 'Vault not configured') {
        await vault.setEnabled(true, await promptPassphrase())
        await config.save()  // retry once vault is set up
        return
    }
    throw e
}

Prevention

When it happens

Trigger: Calling config save/serialize with `store.encrypted === true` while `this.vault.store` is null. Occurs when the `encrypted` flag was toggled on (or carried over from a synced config) without first calling `VaultService.setEnabled(true, passphrase)` to create a vault, or after the vault was disabled/cleared but the `encrypted` flag remained true.

Common situations: Importing a config from config-sync whose `encrypted: true` was set on another machine whose vault passphrase was never set locally; manually editing config to set `encrypted: true`; a partial migration where `setEnabled(false)` cleared the vault store but a stale `encrypted` flag persisted.

Related errors


AI-assisted analysis of Eugeny/tabby@14e2d60b9b (2026-08-12). Data as JSON: /api/errors/538b1a7cda7845f5. Report an issue: GitHub.