Codex switch: translate settings-index ↔ rotation-index by auth path (#51)

settings-store lists codex accounts as
  index 0 = ~/.codex/auth.json (default)
  index N = ~/.codex-accounts/{N}/auth.json
but codex-token-rotation only loads ~/.codex-accounts/{N} when those
dirs exist (it ignores ~/.codex/auth.json in that mode), so the
rotation array's indices are off-by-one vs settings indices.

Old buggy behavior:
- "사용중" badge pointed at the wrong account.
- Clicking 전환 on UI #N called setCurrentCodexAccountIndex(N) which
  selected rotation array element N — typically a different
  ~/.codex-accounts/{N+1} entry. Effectively the UI told us a
  different account was now active than what the next codex spawn
  actually used.

Fix
- New findCodexAccountIndexByAuthPath(path) on the rotation module
  exposes a path-based lookup.
- New getActiveCodexSettingsIndex() / setActiveCodexSettingsIndex(idx)
  in settings-store translate via codexAuthPath() ↔
  findCodexAccountIndexByAuthPath().
- /api/settings/accounts.codexCurrentIndex and PUT
  /api/settings/accounts/codex/current both go through the
  settings-side translation, so UI indices are now authoritative.

Co-authored-by: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
Eyejoker
2026-04-27 22:47:40 +09:00
committed by GitHub
parent 4ed12332d4
commit e2dd12a235
3 changed files with 69 additions and 7 deletions

View File

@@ -15,6 +15,12 @@ import fs from 'node:fs';
import os from 'node:os';
import path from 'node:path';
import {
findCodexAccountIndexByAuthPath,
getActiveCodexAuthPath,
setCurrentCodexAccountIndex as setRotationIndex,
} from './codex-token-rotation.js';
export interface ClaudeAccountSummary {
index: number;
expiresAt: number | null;
@@ -441,6 +447,54 @@ export async function refreshCodexAccount(
return summary;
}
/**
* settings-store lists codex accounts in an order that includes the default
* `~/.codex/auth.json` as index 0 plus `~/.codex-accounts/{N}` as index N.
* The rotation array (codex-token-rotation) only loads `~/.codex-accounts/{N}`
* when those dirs exist (it ignores `~/.codex/auth.json` in that mode), so its
* array indices are off-by-one vs. the settings indices. Translate via path.
*/
export function getActiveCodexSettingsIndex(): number | null {
const activePath = getActiveCodexAuthPath();
if (!activePath) return null;
// Walk the same listing order as listCodexAccounts() to find the matching
// settings-store index.
if (codexAuthPath(0) === activePath && fs.existsSync(activePath)) {
return 0;
}
const dir = path.join(os.homedir(), '.codex-accounts');
if (fs.existsSync(dir)) {
const indices = fs
.readdirSync(dir)
.filter((e) => /^\d+$/.test(e))
.map((e) => Number.parseInt(e, 10))
.filter((n) => Number.isFinite(n) && n >= 1)
.sort((a, b) => a - b);
for (const i of indices) {
if (codexAuthPath(i) === activePath) return i;
}
}
return null;
}
export function setActiveCodexSettingsIndex(settingsIndex: number): void {
const file = codexAuthPath(settingsIndex);
if (!fs.existsSync(file)) {
throw new Error(
`codex auth.json not found for settings index ${settingsIndex}`,
);
}
const rotationIndex = findCodexAccountIndexByAuthPath(file);
if (rotationIndex === null) {
throw new Error(
`codex switch: settings #${settingsIndex} (${file}) is not part of the rotation pool. ` +
`Rotation only manages accounts under ~/.codex-accounts/. ` +
`If you want to use ~/.codex/auth.json directly, remove the ~/.codex-accounts dir first.`,
);
}
setRotationIndex(rotationIndex);
}
export async function refreshAllCodexAccounts(): Promise<{
refreshed: number[];
failed: Array<{ index: number; error: string }>;