fix(auth): dedupe codex-cli profiles

Co-authored-by: Oliver Drobnik <oliver@cocoanetics.com>
This commit is contained in:
Peter Steinberger
2026-01-20 09:37:04 +00:00
parent e0e33e12d1
commit 9c2c4b1138
6 changed files with 344 additions and 39 deletions

View File

@@ -58,6 +58,26 @@ function isExternalProfileFresh(cred: AuthProfileCredential | undefined, now: nu
return cred.expires > now + EXTERNAL_CLI_NEAR_EXPIRY_MS;
}
/**
* Find any existing openai-codex profile (other than codex-cli) that has the same
* access and refresh tokens. This prevents creating a duplicate codex-cli profile
* when the user has already set up a custom profile with the same credentials.
*/
export function findDuplicateCodexProfile(
store: AuthProfileStore,
creds: OAuthCredential,
): string | undefined {
for (const [profileId, profile] of Object.entries(store.profiles)) {
if (profileId === CODEX_CLI_PROFILE_ID) continue;
if (profile.type !== "oauth") continue;
if (profile.provider !== "openai-codex") continue;
if (profile.access === creds.access && profile.refresh === creds.refresh) {
return profileId;
}
}
return undefined;
}
/**
* Sync OAuth credentials from external CLI tools (Claude Code CLI, Codex CLI) into the store.
* This allows clawdbot to use the same credentials as these tools without requiring
@@ -143,31 +163,55 @@ export function syncExternalCliCredentials(
// Sync from Codex CLI
const existingCodex = store.profiles[CODEX_CLI_PROFILE_ID];
const existingCodexOAuth = existingCodex?.type === "oauth" ? existingCodex : undefined;
const duplicateExistingId = existingCodexOAuth
? findDuplicateCodexProfile(store, existingCodexOAuth)
: undefined;
if (duplicateExistingId) {
delete store.profiles[CODEX_CLI_PROFILE_ID];
mutated = true;
log.info("removed codex-cli profile: credentials already exist in another profile", {
existingProfileId: duplicateExistingId,
removedProfileId: CODEX_CLI_PROFILE_ID,
});
}
const shouldSyncCodex =
!existingCodex ||
existingCodex.provider !== "openai-codex" ||
!isExternalProfileFresh(existingCodex, now);
const codexCreds = shouldSyncCodex
const codexCreds = shouldSyncCodex || duplicateExistingId
? readCodexCliCredentialsCached({ ttlMs: EXTERNAL_CLI_SYNC_TTL_MS })
: null;
if (codexCreds) {
const existing = store.profiles[CODEX_CLI_PROFILE_ID];
const existingOAuth = existing?.type === "oauth" ? existing : undefined;
const duplicateProfileId = findDuplicateCodexProfile(store, codexCreds);
if (duplicateProfileId) {
if (store.profiles[CODEX_CLI_PROFILE_ID]) {
delete store.profiles[CODEX_CLI_PROFILE_ID];
mutated = true;
log.info("removed codex-cli profile: credentials already exist in another profile", {
existingProfileId: duplicateProfileId,
removedProfileId: CODEX_CLI_PROFILE_ID,
});
}
} else {
const existing = store.profiles[CODEX_CLI_PROFILE_ID];
const existingOAuth = existing?.type === "oauth" ? existing : undefined;
// Codex creds don't carry expiry; use file mtime heuristic for freshness.
const shouldUpdate =
!existingOAuth ||
existingOAuth.provider !== "openai-codex" ||
existingOAuth.expires <= now ||
codexCreds.expires > existingOAuth.expires;
// Codex creds don't carry expiry; use file mtime heuristic for freshness.
const shouldUpdate =
!existingOAuth ||
existingOAuth.provider !== "openai-codex" ||
existingOAuth.expires <= now ||
codexCreds.expires > existingOAuth.expires;
if (shouldUpdate && !shallowEqualOAuthCredentials(existingOAuth, codexCreds)) {
store.profiles[CODEX_CLI_PROFILE_ID] = codexCreds;
mutated = true;
log.info("synced openai-codex credentials from codex cli", {
profileId: CODEX_CLI_PROFILE_ID,
expires: new Date(codexCreds.expires).toISOString(),
});
if (shouldUpdate && !shallowEqualOAuthCredentials(existingOAuth, codexCreds)) {
store.profiles[CODEX_CLI_PROFILE_ID] = codexCreds;
mutated = true;
log.info("synced openai-codex credentials from codex cli", {
profileId: CODEX_CLI_PROFILE_ID,
expires: new Date(codexCreds.expires).toISOString(),
});
}
}
}