11import { createHash } from "node:crypto" ;
22import { closeSync , existsSync , readFileSync , mkdirSync , openSync , unlinkSync , writeFileSync } from "node:fs" ;
33import { join } from "node:path" ;
4- import { getConfigDir , atomicWriteFile , backupInvalidConfig , hardenConfigDir , hardenExistingSecret , withConfigMutationLockSync } from "../config" ;
4+ import {
5+ ConfigMutationLockError ,
6+ getConfigDir ,
7+ atomicWriteFile ,
8+ backupInvalidConfig ,
9+ hardenConfigDir ,
10+ hardenExistingSecret ,
11+ withConfigMutationLockSync ,
12+ } from "../config" ;
513import { assertNotRealHomeUnderTest } from "../lib/test-home-guard" ;
614import type { CodexAccountCredentialRecord , CodexAccountCredentials } from "../types" ;
715
@@ -121,7 +129,7 @@ export function getCodexAccountCredential(id: string): CodexAccountCredentials |
121129}
122130
123131export function saveCodexAccountCredential ( id : string , cred : CodexAccountCredentials ) : void {
124- withConfigMutationLockSync ( ( ) => {
132+ withCredentialMutationLockSync ( ( ) => {
125133 const store = loadCodexAccountRecordStore ( ) ;
126134 const current = store [ id ] ;
127135 const refreshGrantFingerprint = current ?. credential ?. refreshToken === cred . refreshToken
@@ -139,7 +147,7 @@ export function saveCodexAccountCredential(id: string, cred: CodexAccountCredent
139147}
140148
141149export function markCodexAccountValidated ( id : string , atMs : number = Date . now ( ) ) : void {
142- withConfigMutationLockSync ( ( ) => {
150+ withCredentialMutationLockSync ( ( ) => {
143151 const store = loadCodexAccountRecordStore ( ) ;
144152 const current = store [ id ] ;
145153 if ( ! current || current . deletedAt != null || ! current . credential ) return ;
@@ -154,7 +162,7 @@ export function markCodexAccountValidated(id: string, atMs: number = Date.now())
154162}
155163
156164export function markCodexAccountValidationFailed ( id : string , reason : string ) : void {
157- withConfigMutationLockSync ( ( ) => {
165+ withCredentialMutationLockSync ( ( ) => {
158166 const store = loadCodexAccountRecordStore ( ) ;
159167 const current = store [ id ] ;
160168 if ( ! current || current . deletedAt != null || ! current . credential ) return ;
@@ -189,7 +197,7 @@ export function saveCodexAccountCredentialIfGeneration(
189197 generation : number ,
190198 cred : CodexAccountCredentials ,
191199) : boolean {
192- return withConfigMutationLockSync ( ( ) => {
200+ return withCredentialMutationLockSync ( ( ) => {
193201 const store = loadCodexAccountRecordStore ( ) ;
194202 const current = store [ id ] ;
195203 if ( ! current || current . generation !== generation || current . deletedAt != null || ! current . credential ) {
@@ -211,7 +219,7 @@ export function saveCodexAccountCredentialIfGeneration(
211219}
212220
213221export function tombstoneCodexAccount ( id : string ) : number {
214- return withConfigMutationLockSync ( ( ) => {
222+ return withCredentialMutationLockSync ( ( ) => {
215223 const store = loadCodexAccountRecordStore ( ) ;
216224 const current = store [ id ] ;
217225 const generation = ( current ?. generation ?? 0 ) + 1 ;
@@ -247,6 +255,16 @@ export class CodexCredentialRefreshLockTimeoutError extends Error {
247255 }
248256}
249257
258+ /** Credential writers share the config mutation coordinator; contention is transient, not reauth. */
259+ function withCredentialMutationLockSync < T > ( fn : ( ) => T ) : T {
260+ try {
261+ return withConfigMutationLockSync ( fn ) ;
262+ } catch ( error ) {
263+ if ( error instanceof ConfigMutationLockError ) throw new CodexCredentialRefreshLockTimeoutError ( ) ;
264+ throw error ;
265+ }
266+ }
267+
250268type CodexTokenResult = { accessToken : string ; chatgptAccountId : string ; generation : number } ;
251269type CodexRefreshResult = CodexTokenResult & { credential ?: CodexAccountCredentials } ;
252270const refreshLocks = new Map < string , Promise < CodexRefreshResult > > ( ) ;
0 commit comments