π File detail
utils/secureStorage/fallbackStorage.ts
π§© .tsπ 71 linesπΎ 2,362 bytesπ text
β Back to All Filesπ― Use case
This file lives under βutils/β, which covers cross-cutting helpers (shell, tempfiles, settings, messages, process input, β¦). On the API surface it exposes createFallbackStorage β mainly functions, hooks, or classes. It composes internal code from types (relative imports).
Generated from folder role, exports, dependency roots, and inline comments β not hand-reviewed for every path.
π§ Inline summary
import type { SecureStorage, SecureStorageData } from './types.js' /** * Creates a fallback storage that tries to use the primary storage first, * and if that fails, falls back to the secondary storage
π€ Exports (heuristic)
createFallbackStorage
π₯οΈ Source preview
import type { SecureStorage, SecureStorageData } from './types.js'
/**
* Creates a fallback storage that tries to use the primary storage first,
* and if that fails, falls back to the secondary storage
*/
export function createFallbackStorage(
primary: SecureStorage,
secondary: SecureStorage,
): SecureStorage {
return {
name: `${primary.name}-with-${secondary.name}-fallback`,
read(): SecureStorageData {
const result = primary.read()
if (result !== null && result !== undefined) {
return result
}
return secondary.read() || {}
},
async readAsync(): Promise<SecureStorageData | null> {
const result = await primary.readAsync()
if (result !== null && result !== undefined) {
return result
}
return (await secondary.readAsync()) || {}
},
update(data: SecureStorageData): { success: boolean; warning?: string } {
// Capture state before update
const primaryDataBefore = primary.read()
const result = primary.update(data)
if (result.success) {
// Delete secondary when migrating to primary for the first time
// This preserves credentials when sharing .claude between host and containers
// See: https://github.com/anthropics/claude-code/issues/1414
if (primaryDataBefore === null) {
secondary.delete()
}
return result
}
const fallbackResult = secondary.update(data)
if (fallbackResult.success) {
// Primary write failed but primary may still hold an *older* valid
// entry. read() prefers primary whenever it returns non-null, so that
// stale entry would shadow the fresh data we just wrote to secondary β
// e.g. a refresh token the server has already rotated away, causing a
// /login loop (#30337). Best-effort delete; if this also fails the
// user's keychain is in a bad state we can't fix from here.
if (primaryDataBefore !== null) {
primary.delete()
}
return {
success: true,
warning: fallbackResult.warning,
}
}
return { success: false }
},
delete(): boolean {
const primarySuccess = primary.delete()
const secondarySuccess = secondary.delete()
return primarySuccess || secondarySuccess
},
}
}