/** * Cooperate with Claude Code's OWN advisory lock while we swap credentials. * * Why: Claude refreshes its OAuth token in the background. It takes this lock, * reads the credential, decides it is near expiry, refreshes, and writes the * result back. A swap landing inside that window can be overwritten by the * refreshed OLD account's token. Holding the same lock for the few milliseconds * of our swap closes that window: Claude waits, and its post-lock re-read then * sees our fresh (non-expired) credential and skips its own refresh. * * The lock is a DIRECTORY (mkdir is atomic across processes), matching the * lockfile convention Claude uses: `/.oauth_refresh.lock`. * * Deliberately BEST EFFORT with a bounded wait. Claude Code ships as a compiled * binary, so its exact staleness constants are not readable; guessing wrong and * blocking would turn a rare race into a guaranteed hang. If the lock cannot be * taken quickly we proceed anyway, which is exactly the (working) behavior we * had before this existed, only now the common case is properly serialized. */ export declare const CREDENTIALS_LOCK_DIR = ".oauth_refresh.lock"; export interface LockOptions { /** Give up waiting after this long and proceed unlocked. */ waitMs?: number; /** Only take over a lock whose mtime is older than this (assume abandoned). */ staleMs?: number; /** Refresh our own lock's mtime this often so others do not judge it stale. */ touchMs?: number; now?: () => number; } export interface LockHandle { /** True when we actually hold the lock (false = proceeding without it). */ held: boolean; release(): void; } /** * Try to take the lock directory. Returns a handle that is `held: false` when * the wait elapsed; callers proceed either way and must always release(). */ export declare function acquireLockDir(lockDir: string, options?: LockOptions): LockHandle; /** * Run `fn` while holding Claude's credential lock for `configDir` (best effort). * The lock is always released, including when `fn` throws. */ /** * Run `fn` under the credential lock, but ONLY if the lock is free right now. * Returns false, without running `fn`, when something else holds it. * * For opportunistic work that runs on a timer. The wait inside acquireLockDir is * a synchronous sleep loop (up to two seconds by default), so waiting there from * a timer would stall whatever loop is driving it: on the session's poll that * means freezing the terminal relay. Skipping and trying again on the next tick * costs nothing. */ export declare function withCredentialLockIfFree(configDir: string, fn: () => void): boolean; export declare function withCredentialLock(configDir: string, fn: () => T, options?: LockOptions): T;