{"record":{"id":"33fb216035351538","repo":"Eugeny/tabby","slug":"vault-unlock-cancelled","errorCode":null,"errorMessage":"Vault unlock cancelled","messagePattern":"Vault unlock cancelled","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"warning","filePath":"tabby-core/src/services/vault.service.ts","lineNumber":183,"sourceCode":"        }\n        if (_rememberedPassphrase) {\n            _rememberedPassphrase = passphrase\n        }\n        return wrapPromise(this.zone, encryptVault(vault, passphrase))\n    }\n\n    async save (vault: Vault, passphrase?: string): Promise<void> {\n        await this.ready$.toPromise()\n        this.store = await this.encrypt(vault, passphrase)\n        this.contentChanged.next()\n    }\n\n    async getPassphrase (): Promise<string> {\n        if (!_rememberedPassphrase) {\n            const modal = this.ngbModal.open(UnlockVaultModalComponent)\n            const result = await modal.result.catch(() => null)\n            if (!result) {\n                throw new Error('Vault unlock cancelled')\n            }\n            const { passphrase, rememberFor } = result\n            setTimeout(() => {\n                _rememberedPassphrase = null\n                // avoid multiple consequent prompts\n            }, Math.max(1000, rememberFor * 60000))\n            _rememberedPassphrase = passphrase\n        }\n\n        return _rememberedPassphrase!\n    }\n\n    async getSecret (type: string, key: VaultSecretKey): Promise<VaultSecret|null> {\n        await this.ready$.toPromise()\n        const vault = await this.load()\n        if (!vault) {\n            return null\n        }","sourceCodeStart":165,"sourceCodeEnd":201,"githubUrl":"https://github.com/Eugeny/tabby/blob/14e2d60b9b6dee84a53c37f05eefeb803787de04/tabby-core/src/services/vault.service.ts#L165-L201","documentation":"Thrown by `VaultService.getPassphrase` when the unlock modal is dismissed or returns a falsy result (user clicked cancel/closed the dialog/pressed ESC). Since there is no remembered passphrase, the only way to obtain one is the modal; cancelling it leaves no passphrase to proceed, so the operation aborts.","triggerScenarios":"Any vault operation that needs the passphrase (decrypt, encrypt, getSecret, addSecret, retrieveFile via VaultFileProvider) when `_rememberedPassphrase` is null and the user dismisses the `UnlockVaultModalComponent`. The modal's `.result` promise rejects on dismiss, which is caught and normalized to null.","commonSituations":"User cancels the master-passphrase prompt because they forgot it, do not want to unlock right now, or hit ESC by accident; an automated/scripted flow has no UI to answer the modal.","solutions":["Wrap the calling operation in try/catch and treat 'Vault unlock cancelled' as a user-initiated cancel (skip the action, do not retry automatically).","If the operation is mandatory, re-prompt the user with clearer messaging rather than silently failing.","Pre-unlock the vault at app start (via a login flow) so dependent operations already have `_rememberedPassphrase` set.","For headless contexts, supply the passphrase programmatically to `vault.decrypt(store, passphrase)` / `encrypt(vault, passphrase)` to bypass the modal entirely."],"exampleFix":"// before\nconst vault = await this.vault.load()  // may throw 'Vault unlock cancelled'\n\n// after - treat cancel as a soft abort\ntry {\n    const vault = await this.vault.load()\n    return vault\n} catch (e) {\n    if (e instanceof Error && e.message === 'Vault unlock cancelled') {\n        return null  // user chose not to unlock; degrade gracefully\n    }\n    throw e\n}","handlingStrategy":"try-catch","validationCode":"async function tryWithPassphrase (vault: VaultService, passphrase: string | null, fn: () => Promise<any>) {\n    if (passphrase) {\n        // pass explicitly to bypass the modal entirely\n        return fn()\n    }\n    return fn()  // will trigger modal; caller must handle cancel\n}","typeGuard":null,"tryCatchPattern":"try {\n    return await vault.load()\n} catch (e) {\n    if (e instanceof Error && e.message === 'Vault unlock cancelled') {\n        return null  // graceful: user declined to unlock\n    }\n    throw e\n}","preventionTips":["Unlock the vault once at session start so subsequent operations have a remembered passphrase.","Always treat 'Vault unlock cancelled' as a benign user choice, not an error to retry.","Pass an explicit passphrase to decrypt/encrypt in automated flows to avoid the modal.","Distinguish cancel from wrong-passphrase (different error path) in the caller."],"tags":["vault","passphrase","modal","user-cancel","ui"],"backgroundTag":null,"analyzedSha":"14e2d60b9b6dee84a53c37f05eefeb803787de04","analyzedAt":"2026-08-12T11:46:48.773Z","schemaVersion":2},"datasetVersion":"2026-08-12T18:17:37.767Z"}