{"record":{"id":"538b1a7cda7845f5","repo":"Eugeny/tabby","slug":"vault-not-configured","errorCode":null,"errorMessage":"Vault not configured","messagePattern":"Vault not configured","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"tabby-core/src/services/config.service.ts","lineNumber":537,"sourceCode":"        }\n        delete decryptedVault.config.vault\n        delete decryptedVault.config.encrypted\n        delete decryptedVault.config.configSync\n        return {\n            ...decryptedVault.config,\n            vault: store.vault,\n            encrypted: store.encrypted,\n            configSync: store.configSync,\n        }\n    }\n\n    private async maybeEncryptConfig (store) {\n        if (!store.encrypted) {\n            return store\n        }\n        const vault = await this.vault.load()\n        if (!vault) {\n            throw new Error('Vault not configured')\n        }\n        vault.config = { ...store }\n        delete vault.config.vault\n        delete vault.config.encrypted\n        delete vault.config.configSync\n        return {\n            vault: await this.vault.encrypt(vault),\n            encrypted: true,\n            configSync: store.configSync,\n        }\n    }\n}\n","sourceCodeStart":519,"sourceCodeEnd":550,"githubUrl":"https://github.com/Eugeny/tabby/blob/14e2d60b9b6dee84a53c37f05eefeb803787de04/tabby-core/src/services/config.service.ts#L519-L550","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Set up the vault before enabling encryption: call `vault.setEnabled(true, passphrase)` so a StoredVault is created, then set `store.encrypted = true`.","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.","Prompt the user for a master passphrase via the unlock modal flow before triggering `maybeEncryptConfig` when encryption is requested.","Validate the invariant early: in config load, if `store.encrypted && !vault.store`, either trigger vault setup or downgrade to plaintext with a notification."],"exampleFix":"// before\nprivate async maybeEncryptConfig (store) {\n    if (!store.encrypted) return store\n    const vault = await this.vault.load()\n    if (!vault) throw new Error('Vault not configured')\n    ...\n}\n\n// after - ensure vault exists before encryption is allowed\nif (!store.encrypted) return store\nif (!this.vault.isEnabled() || !(await this.vault.load())) {\n    await this.vault.setEnabled(true, await promptPassphrase())\n}\nconst vault = await this.vault.load()","handlingStrategy":"validation","validationCode":"async function ensureVaultBeforeEncrypt (vault: VaultService, store: any): Promise<void> {\n    if (!store.encrypted) return\n    if (!vault.isEnabled() || !(await vault.load())) {\n        await vault.setEnabled(true, await promptForMasterPassphrase())\n    }\n    if (!(await vault.load())) {\n        throw new Error('Vault not configured: refusing to mark config encrypted without an unlocked vault')\n    }\n}","typeGuard":"function isStoredVaultReady (v: unknown): v is { version: number; contents: string; keySalt: string; iv: string } {\n    return typeof v === 'object' && v !== null &&\n        typeof (v as any).version === 'number' &&\n        typeof (v as any).contents === 'string' &&\n        typeof (v as any).keySalt === 'string' &&\n        typeof (v as any).iv === 'string'\n}","tryCatchPattern":"try {\n    await config.save()\n} catch (e) {\n    if (e instanceof Error && e.message === 'Vault not configured') {\n        await vault.setEnabled(true, await promptPassphrase())\n        await config.save()  // retry once vault is set up\n        return\n    }\n    throw e\n}","preventionTips":["Treat `store.encrypted` and vault state as a single invariant: change them together.","On config load, if `encrypted && !vault.store`, either trigger setup or downgrade to plaintext with a notification.","Never set `encrypted: true` via raw config edits; always go through VaultService.setEnabled.","When importing synced config, strip or reconcile the `encrypted` flag against local vault state before merge."],"tags":["vault","encryption","config","security","configuration"],"backgroundTag":null,"analyzedSha":"14e2d60b9b6dee84a53c37f05eefeb803787de04","analyzedAt":"2026-08-12T11:46:48.773Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}