source dump of claude code
at main 70 lines 2.4 kB view raw
1import type { SecureStorage, SecureStorageData } from './types.js' 2 3/** 4 * Creates a fallback storage that tries to use the primary storage first, 5 * and if that fails, falls back to the secondary storage 6 */ 7export function createFallbackStorage( 8 primary: SecureStorage, 9 secondary: SecureStorage, 10): SecureStorage { 11 return { 12 name: `${primary.name}-with-${secondary.name}-fallback`, 13 read(): SecureStorageData { 14 const result = primary.read() 15 if (result !== null && result !== undefined) { 16 return result 17 } 18 return secondary.read() || {} 19 }, 20 async readAsync(): Promise<SecureStorageData | null> { 21 const result = await primary.readAsync() 22 if (result !== null && result !== undefined) { 23 return result 24 } 25 return (await secondary.readAsync()) || {} 26 }, 27 update(data: SecureStorageData): { success: boolean; warning?: string } { 28 // Capture state before update 29 const primaryDataBefore = primary.read() 30 31 const result = primary.update(data) 32 33 if (result.success) { 34 // Delete secondary when migrating to primary for the first time 35 // This preserves credentials when sharing .claude between host and containers 36 // See: https://github.com/anthropics/claude-code/issues/1414 37 if (primaryDataBefore === null) { 38 secondary.delete() 39 } 40 return result 41 } 42 43 const fallbackResult = secondary.update(data) 44 45 if (fallbackResult.success) { 46 // Primary write failed but primary may still hold an *older* valid 47 // entry. read() prefers primary whenever it returns non-null, so that 48 // stale entry would shadow the fresh data we just wrote to secondary — 49 // e.g. a refresh token the server has already rotated away, causing a 50 // /login loop (#30337). Best-effort delete; if this also fails the 51 // user's keychain is in a bad state we can't fix from here. 52 if (primaryDataBefore !== null) { 53 primary.delete() 54 } 55 return { 56 success: true, 57 warning: fallbackResult.warning, 58 } 59 } 60 61 return { success: false } 62 }, 63 delete(): boolean { 64 const primarySuccess = primary.delete() 65 const secondarySuccess = secondary.delete() 66 67 return primarySuccess || secondarySuccess 68 }, 69 } 70}