Skip to main content

Overview

Codex Multi-Auth implements enterprise-grade storage safety features to protect your OAuth credentials and account data from corruption, concurrent access issues, and data loss.

Storage v3 Format

The latest storage format provides improved structure and reliability.

Per-Family Active Index

Tracks separate active accounts for each model family (GPT-5.x, Codex variants) to support family-specific routing.

Structured Rate Limits

Stores rate limit reset times per quota key (codex:gpt-5-o1, codex:claude-5-sonnet) for accurate cooldown tracking.

Enhanced Metadata

Includes accountId source tracking, account labels, cooldown reasons, and per-account enable/disable flags.

Backward Compatible

Automatically migrates v1 and v2 storage formats on first load with zero data loss.

Storage Structure

{
  "version": 3,
  "activeIndex": 1,
  "activeIndexByFamily": {
    "gpt-5-o1": 0,
    "gpt-5-o3-mini": 1,
    "claude-5-sonnet": 2
  },
  "accounts": [
    {
      "accountId": "user-abc123",
      "accountIdSource": "id_token",
      "email": "[email protected]",
      "accountLabel": "Work Account",
      "refreshToken": "ey...",
      "accessToken": "ey...",
      "expiresAt": 1709484834567,
      "enabled": true,
      "addedAt": 1709394834567,
      "lastUsed": 1709481234567,
      "lastSwitchReason": "rate-limit",
      "rateLimitResetTimes": {
        "codex": 1709481534567,
        "codex:gpt-5-o1": 1709481834567
      },
      "coolingDownUntil": 1709481534567,
      "cooldownReason": "rate-limit"
    }
  ]
}

Field Reference

FieldTypeDescription
versionnumberStorage format version (always 3)
activeIndexnumberGlobal active account index
activeIndexByFamilyobjectPer-model-family active account mapping
accountIdstringUnique account identifier from OAuth
accountIdSourceenumSource of accountId: token, id_token, org, manual
emailstringAccount email (case-insensitive dedupe)
accountLabelstringUser-assigned label for account
refreshTokenstringOAuth refresh token (encrypted at rest)
accessTokenstringCached access token
expiresAtnumberToken expiry timestamp (ms since epoch)
enabledbooleanAccount enable/disable flag
addedAtnumberAccount creation timestamp
lastUsednumberLast successful request timestamp
lastSwitchReasonenumWhy account was last switched: rate-limit, initial, rotation
rateLimitResetTimesobjectPer-quota-key rate limit reset times
coolingDownUntilnumberCooldown expiry timestamp
cooldownReasonenumCooldown trigger: auth-failure, network-error, rate-limit

Atomic Write Strategy

All storage operations use atomic writes to prevent corruption from crashes or interruptions.

Write-Ahead Logging (WAL)

// Write sequence:
1. Create WAL journal with content + SHA256 checksum
2. Write to temp file (.tmp)
3. Verify temp file size > 0
4. Atomic rename tempaccounts.json
5. Delete WAL journal on success
Benefits:
  • Crash recovery from WAL journal if main file corrupted
  • Checksum validation prevents partial write corruption
  • Platform-safe atomic rename (handles Windows EBUSY/EPERM)

Backup Files

Automatic backup before each write:
~/.codex/multi-auth/
├── openai-codex-accounts.json      # Primary storage
├── openai-codex-accounts.json.bak  # Automatic backup
└── openai-codex-accounts.json.wal  # WAL journal (during writes)
Recovery Priority:
  1. Primary file (accounts.json)
  2. WAL journal (.wal) if primary corrupted
  3. Backup file (.bak) if both primary and WAL fail

Concurrency Safety

// Storage mutex prevents concurrent writes
await withAccountStorageTransaction(async (current, persist) => {
  const updated = { ...current };
  updated.accounts.push(newAccount);
  await persist(updated);
});
All mutations use a queue-based mutex to prevent:
  • Race conditions from concurrent plugin instances
  • Partial writes from overlapping operations
  • Lost updates from read-modify-write conflicts

Storage Locations

Global Storage

Default location for all accounts:
~/.codex/multi-auth/openai-codex-accounts.json
Used when:
  • No project root detected
  • Working outside a git repository
  • Explicitly configured via setStoragePathDirect()

Project-Scoped Storage

Per-project account isolation:
# For repository at ~/projects/my-app
~/projects/my-app/.codex/openai-codex-accounts.json
Used when:
  • Working inside a git repository
  • Project root detected via .git directory
  • Automatic .gitignore entry added for .codex/

Worktree Support

Linked worktrees share accounts via identity root resolution:
# Main repo
~/projects/my-app/.codex/openai-codex-accounts.json

# Linked worktree (shares same storage)
~/worktrees/feature-branch/
  └── .git ~/projects/my-app/.git/worktrees/feature-branch
Resolution Algorithm:
  1. Detect .git file (linked worktree indicator)
  2. Parse gitdir: path from .git file
  3. Read commondir to find repository root
  4. Validate bidirectional gitdir reference
  5. Use common repository root for storage key
This ensures all worktrees from the same repository share accounts.

Automatic Migrations

Seamless upgrades from legacy formats:

V1 → V3 Migration

// V1 Format (single rate limit)
{
  "version": 1,
  "accounts": [{
    "rateLimitResetTime": 1709481234567  // single timestamp
  }]
}

// Migrated to V3 (per-family rate limits)
{
  "version": 3,
  "accounts": [{
    "rateLimitResetTimes": {
      "gpt-5-o1": 1709481234567,
      "gpt-5-o3-mini": 1709481234567,
      "claude-5-sonnet": 1709481234567
    }
  }],
  "activeIndexByFamily": {
    "gpt-5-o1": 0,
    "gpt-5-o3-mini": 0,
    "claude-5-sonnet": 0
  }
}

Worktree-Keyed Storage Migration

Legacy worktree-specific storage is automatically migrated to shared repository storage:
// Old: per-worktree storage (isolated)
~/worktrees/feature-a/.codex/openai-codex-accounts.json
~/worktrees/feature-b/.codex/openai-codex-accounts.json

// New: shared repository storage (merged)
~/projects/my-app/.codex/openai-codex-accounts.json
Migration Process:
  1. Detect legacy worktree storage file
  2. Load and normalize legacy accounts
  3. Merge with current repository storage (dedupe by email/accountId)
  4. Persist merged storage to identity root
  5. Delete legacy file on success
  6. Retry-safe: preserves legacy file if merge fails

Deduplication

Automatic duplicate removal on every load:

By Account Key

// Deduplicates by accountId or refreshToken
const key = account.accountId || account.refreshToken;
// Keeps most recently used account

By Email (Case-Insensitive)

// Normalizes email to lowercase
function normalizeEmailKey(email) {
  return email.trim().toLowerCase();
}

// [email protected] == [email protected] == [email protected]
Precedence: When duplicates exist, keeps account with:
  1. Most recent lastUsed timestamp
  2. If tied, most recent addedAt timestamp

Error Handling

Storage Errors

class StorageError extends Error {
  code: string;        // EACCES, EPERM, EBUSY, ENOSPC
  path: string;        // Affected file path
  hint: string;        // Platform-specific remediation
  cause?: Error;       // Original error with stack trace
}
Platform-Aware Hints:
CodePlatformHint
EACCESWindowsCheck antivirus exclusions for ~/.codex folder
EPERMWindowsEnsure write permissions, close editors accessing file
EBUSYWindowsFile locked by another process, close conflicting programs
ENOSPCAllDisk full, free up space and retry
EACCESLinux/MacRun chmod 755 ~/.codex to fix permissions

Retry Logic

Windows-specific retry for filesystem locks:
// Rename with exponential backoff (EBUSY/EPERM)
for (let attempt = 0; attempt < 5; attempt++) {
  try {
    await fs.rename(tempPath, finalPath);
    return;
  } catch (error) {
    if (error.code === 'EBUSY' || error.code === 'EPERM') {
      await sleep(10 * Math.pow(2, attempt));  // 10, 20, 40, 80, 160 ms
      continue;
    }
    throw error;
  }
}

Import/Export

Export Accounts

codex auth export backup.json
Creates portable backup:
  • Includes all accounts with full metadata
  • Preserves v3 format structure
  • Permissions: 0o600 (owner read/write only)
  • Overwrites existing file (use --no-force to prevent)

Import Accounts

codex auth import backup.json
Merges accounts from backup:
  • Deduplicates by accountId and email
  • Preserves most recent versions
  • Fails if result exceeds max accounts (50)
  • Validates schema before applying
Output:
Imported 2 accounts
Skipped 1 duplicate
Total accounts: 5

Best Practices

Data Safety Recommendations

  1. Enable Backups: Keep storageBackupEnabled: true (default)
  2. Regular Exports: Run codex auth export before major operations
  3. Monitor WAL: Persistent .wal files indicate write failures
  4. Verify Migrations: Check account count after automatic migrations
  5. Gitignore: Ensure .codex/ is in .gitignore to prevent credential leaks
  6. Permissions: Storage files use 0o600 (owner-only access)
  7. Cleanup: Run codex auth fix to deduplicate after imports
  • storageBackupEnabled - Enable/disable automatic backups (default: true)
  • CODEX_PROJECT_ROOT - Override project root detection
  • Account limits defined in ACCOUNT_LIMITS.MAX_ACCOUNTS (50)

Build docs developers (and LLMs) love