Skill: markeljan/codex-multi-subscription-auth-fallbacks (version 1.1.0)

--- name: codex-auth-fallback description: Set up OpenClaw multi-provider auth with OpenAI Codex OAuth fallback profiles and automatic model switching. Use when configuring multiple OpenAI Codex accounts for rate-limit failover, adding new Codex OAuth profiles via device flow, or setting up a cron job to auto-switch models when a provider hits cooldown. requires: - codex # OpenAI Codex CLI (npm i -g @openai/codex) — also provides node files_read: - ~/.codex/auth.json # Reads OAuth tokens after device-flow login files_write: - ~/.codex/auth.json # Temporarily cleared to force fresh login, then restored from backup - ~/.openclaw/agents/main/agent/auth-profiles.json # Writes imported OAuth profile tokens --- # Codex Auth Fallback Multi-provider auth setup for OpenClaw with automatic failover between Anthropic and multiple OpenAI Codex OAuth sessions. ## Overview OpenClaw supports multiple auth profiles per provider. When one profile hits a rate limit, the platform can fail over to another. This skill covers: 1. **Adding Codex OAuth profiles** via device-flow login 2. **Configuring `openclaw.json`** for provider fallback order 3. **Setting up `auth-profiles.json`** with multiple profiles 4. **Deploying a cron job** to auto-switch models on cooldown ## Prerequisites - OpenClaw instance running - `codex` CLI installed (`npm i -g @openai/codex`) — this also ensures `node` is available - One or more OpenAI accounts with Codex access ## Security & Safety **What this skill accesses:** | File | Access | Purpose | |------|--------|---------| | `~/.codex/auth.json` | Read + Temporary Write | Temporarily cleared to force a fresh device-flow login, then restored from backup. Original tokens are never deleted — a timestamped backup is created first. | | `~/.openclaw/agents/main/agent/auth-profiles.json` | Read + Write | Imported OAuth tokens (access + refresh) are written here. A timestamped backup is created before any modification. | **Important safety notes:** - **Tokens stay local.** No tokens are sent to any external endpoint. The script reads tokens from the local Codex CLI auth file and writes them to the local OpenClaw auth-profiles file. - **Backups are always created.** Both files are backed up with timestamps before any modification. If login fails or the script is interrupted, a trap handler restores the original Codex CLI auth automatically. - **Interactive confirmation.** The script prompts for confirmation before clearing the Codex CLI auth file, so you can abort if needed. - **No elevated privileges.** The script runs as your user and does not require sudo or any special permissions. - **Back up manually first.** Despite the automatic backups, it is recommended to manually back up `~/.codex/auth.json` and your OpenClaw configs before running, especially on first use. - **Test with a non-production account.** For initial testing, consider using a throwaway or non-production OpenAI account. ## Step 1: Add Codex OAuth Profiles Run the bundled script for each OpenAI account: ```bash ./scripts/codex-add-profile.sh <profile-name> ``` The script: 1. Backs up `~/.codex/auth.json` and `auth-profiles.json` 2. Clears Codex CLI auth to force fresh device-flow login 3. Runs `codex auth login` (opens browser for OAuth) 4. Extracts tokens and imports them into OpenClaw's `auth-profiles.json` 5. Restores the original Codex CLI auth Repeat for each account. Profile names should be short identifiers (e.g., the OpenAI username). ## Step 2: Configure openclaw.json Add auth profile declarations and fallback model config. See `references/config-templates.md` for the exact JSON blocks to add to `openclaw.json`. Key sections: - **`auth.profiles`** — Declare each profile with provider and mode - **`auth.order`** — Set failover priority per provider - **`agents.defaults.model`** — Set primary model + fallbacks ## Step 3: Auth Profiles JSON Structure OpenClaw stores live tokens in `agents/main/agent/auth-profiles.json`. See `references/config-templates.md` for the schema. Each Codex profile contains: - `type`: `"oauth"` - `provider`: `"openai-codex"` - `access`: JWT access token (auto-populated by the add-profile script) - `refresh`: Refresh token (auto-populated) - `expires`: Token expiry in ms (parsed from JWT) - `accountId`: OpenAI account ID (parsed from JWT) The `order` object controls which profile is tried first per provider. The `usageStats` object tracks rate limits and cooldowns automatically. ## Step 4: Model Cooldown Auto-Switch Cron (Optional) > **This step is entirely optional.** The auth profiles from Steps 1-3 work on their own with OpenClaw's built-in failover. This cron job adds automatic model switching, which means your active model may change without manual intervention. Only enable it if you understand and want this behavior. Deploy a cron job that checks cooldown state every 10 minutes and switches the active model. See `references/config-templates.md` for the full cron job definition. The cron job: 1. Runs `openclaw models status` to check cooldown state 2. Picks the best available model (priority: opus > codex profiles in order) 3. Updates the session model override if needed 4. Logs state to a local memory file; only notifies on change **Before enabling:** - Test manually first: run `openclaw models status` to verify your profiles are working - Review the cron job template in `references/config-templates.md` — the job only runs local commands and writes to a local state file - The job runs in an isolated session and does not affect your main chat unless a model switch occurs Add the job to `cron/jobs.json` using the template in the references. ## File Layout ``` codex-auth-fallback/ ├── SKILL.md # This file ├── scripts/ │ └── codex-add-profile.sh # Device-flow profile importer └── references/ └── config-templates.md # openclaw.json, auth-profiles, cron templates ```

path: scripts/codex-add-profile.sh
content: #!/usr/bin/env bash
# codex-add-profile.sh — Run Codex CLI OAuth device-flow login and import
# the tokens into openclaw as a named auth profile, without overwriting
# existing profiles.
#
# Usage: ./codex-add-profile.sh <profile-name>
# Example: ./codex-add-profile.sh myaccount
#
# Requires: codex CLI (`npm i -g @openai/codex`), node
#
# Files read:
#   ~/.codex/auth.json — OAuth tokens after device-flow login
# Files written:
#   ~/.codex/auth.json — Temporarily cleared, then restored from backup
#   ~/.openclaw/agents/main/agent/auth-profiles.json — Imported profile tokens
#
# Safety: Both files are backed up before modification. A trap handler
# restores ~/.codex/auth.json automatically on unexpected exit.

set -euo pipefail

PROFILE_NAME="${1:?Usage: $0 <profile-name>}"
OPENCLAW_AUTH="$HOME/.openclaw/agents/main/agent/auth-profiles.json"
CODEX_AUTH="$HOME/.codex/auth.json"
CODEX_BACKUP="${CODEX_AUTH}.bak-$(date +%s)"
OPENCLAW_BACKUP="${OPENCLAW_AUTH}.bak-$(date +%s)"

# Track whether we need to restore codex auth on unexpected exit
CODEX_AUTH_CLEARED=0

# Trap handler: restore codex auth if script exits unexpectedly after clearing it
cleanup() {
  if [ "$CODEX_AUTH_CLEARED" -eq 1 ] && [ -f "$CODEX_BACKUP" ]; then
    echo ""
    echo "==> Interrupted. Restoring Codex CLI auth from backup..."
    cp "$CODEX_BACKUP" "$CODEX_AUTH"
    echo "    Restored."
  fi
}
trap cleanup EXIT INT TERM

# Check all required dependencies upfront
command -v codex >/dev/null 2>&1 || { echo "Error: codex CLI not found. Install with: npm i -g @openai/codex"; exit 1; }
command -v node >/dev/null 2>&1 || { echo "Error: node not found (required by codex CLI)"; exit 1; }

echo "==> Adding openai-codex:$PROFILE_NAME via Codex CLI device flow"
echo ""

# Step 1: Back up both files
echo "==> Backing up existing auth files"
if [ -f "$CODEX_AUTH" ]; then
  cp "$CODEX_AUTH" "$CODEX_BACKUP"
  # Verify backup succeeded
  if [ ! -f "$CODEX_BACKUP" ]; then
    echo "Error: Failed to create backup at $CODEX_BACKUP"
    exit 1
  fi
  echo "    Codex CLI:  $CODEX_BACKUP"
fi
if [ -f "$OPENCLAW_AUTH" ]; then
  cp "$OPENCLAW_AUTH" "$OPENCLAW_BACKUP"
  if [ ! -f "$OPENCLAW_BACKUP" ]; then
    echo "Error: Failed to create backup at $OPENCLAW_BACKUP"
    exit 1
  fi
  echo "    OpenClaw:   $OPENCLAW_BACKUP"
fi

# Step 2: Confirm before clearing codex auth
echo ""
echo "==> This will temporarily clear ~/.codex/auth.json to force a fresh login."
echo "    Your existing Codex CLI session has been backed up and will be restored afterward."
echo ""
printf "    Continue? [y/N] "
read -r CONFIRM
if [[ ! "$CONFIRM" =~ ^[Yy]$ ]]; then
  echo "    Aborted."
  exit 0
fi

echo ""
echo "==> Clearing Codex CLI auth to force fresh login..."
echo '{}' > "$CODEX_AUTH"
CODEX_AUTH_CLEARED=1

# Step 3: Run codex auth login (device flow — gives a URL + code)
echo ""
echo "==> Starting Codex CLI device-flow login."
echo "    Log in with your '$PROFILE_NAME' OpenAI account when the browser opens."
echo ""

LOGIN_OK=0
codex auth login && LOGIN_OK=1 || true

if [ "$LOGIN_OK" -eq 0 ] || [ ! -s "$CODEX_AUTH" ]; then
  echo ""
  echo "==> Login failed or cancelled. Restoring Codex CLI backup..."
  [ -f "$CODEX_BACKUP" ] && cp "$CODEX_BACKUP" "$CODEX_AUTH"
  echo "    Restored."
  exit 1
fi

# Step 4: Import tokens into openclaw auth-profiles.json
echo ""
echo "==> Importing tokens into openclaw as openai-codex:$PROFILE_NAME..."
PROFILE_NAME="$PROFILE_NAME" OPENCLAW_AUTH="$OPENCLAW_AUTH" node << 'NODEEOF'
const fs = require("fs");
const path = require("path");

const profileName = process.env.PROFILE_NAME;
const openclawAuthPath = process.env.OPENCLAW_AUTH;
const codexAuthPath = path.join(require("os").homedir(), ".codex", "auth.json");

// Read codex auth
const codex = JSON.parse(fs.readFileSync(codexAuthPath, "utf8"));
const tokens = codex.tokens || {};
if (!tokens.access_token && !tokens.refresh_token) {
  console.error("Error: No OAuth tokens found in codex auth.json");
  process.exit(1);
}

// Build openclaw profile entry
const profileKey = `openai-codex:${profileName}`;
const profileEntry = {
  type: "oauth",
  provider: "openai-codex",
  access: tokens.access_token || "",
  refresh: tokens.refresh_token || "",
};
if (tokens.account_id) profileEntry.accountId = tokens.account_id;

// Calculate expiry from access token JWT (exp claim)
try {
  const parts = tokens.access_token.split(".");
  const payload = JSON.parse(Buffer.from(parts[1], "base64url").toString());
  if (payload.exp) {
    profileEntry.expires = payload.exp * 1000; // ms
    console.log(`    Token expires at epoch ${payload.exp}`);
  }
} catch (e) {
  console.log(`    Warning: could not parse token expiry: ${e.message}`);
}

// Read and update openclaw auth (create if missing)
let data;
if (fs.existsSync(openclawAuthPath)) {
  data = JSON.parse(fs.readFileSync(openclawAuthPath, "utf8"));
} else {
  data = { version: 1, profiles: {}, order: {} };
}
if (!data.profiles) data.profiles = {};
if (!data.order) data.order = {};

// Replace placeholder if it exists
const existing = data.profiles[profileKey] || {};
if (existing.needsAuth) {
  console.log(`    Replacing placeholder for ${profileKey}`);
}

data.profiles[profileKey] = profileEntry;

// Update order list for openai-codex provider
const codexOrder = data.order["openai-codex"] || [];
if (!codexOrder.includes(profileKey)) codexOrder.push(profileKey);
data.order["openai-codex"] = codexOrder;

console.log(`    Saved profile: ${profileKey}`);

// Ensure parent directory exists
fs.mkdirSync(path.dirname(openclawAuthPath), { recursive: true });
fs.writeFileSync(openclawAuthPath, JSON.stringify(data, null, 2));

console.log("    Auth profiles updated successfully");
NODEEOF

# Step 5: Restore the original codex CLI auth
echo ""
echo "==> Restoring original Codex CLI auth..."
if [ -f "$CODEX_BACKUP" ]; then
  cp "$CODEX_BACKUP" "$CODEX_AUTH"
  CODEX_AUTH_CLEARED=0  # Clear flag so trap handler doesn't double-restore
  echo "    Restored original Codex CLI session."
fi

echo ""
echo "==> Done! Profile openai-codex:$PROFILE_NAME added."
echo "    OpenClaw backup: $OPENCLAW_BACKUP"
echo "    Codex backup:    $CODEX_BACKUP"
echo ""
echo "Next: Add the profile to openclaw.json auth.profiles and auth.order"


path: references/config-templates.md
content: # Config Templates

## openclaw.json — Auth Section

Add to `openclaw.json` under `auth`:

```json
{
  "auth": {
    "profiles": {
      "anthropic:<your-anthropic-id>": {
        "provider": "anthropic",
        "mode": "token"
      },
      "openai-codex:<account-1>": {
        "provider": "openai-codex",
        "mode": "oauth"
      },
      "openai-codex:<account-2>": {
        "provider": "openai-codex",
        "mode": "oauth"
      }
    },
    "order": {
      "anthropic": [
        "anthropic:<your-anthropic-id>"
      ],
      "openai-codex": [
        "openai-codex:<account-1>",
        "openai-codex:<account-2>"
      ]
    }
  }
}
```

- **`profiles`** — Declare each profile. `mode` is `"token"` for API keys, `"oauth"` for Codex device-flow tokens.
- **`order`** — Failover priority per provider. First entry is tried first.

## openclaw.json — Model Fallback

Add to `openclaw.json` under `agents.defaults`:

```json
{
  "agents": {
    "defaults": {
      "model": {
        "primary": "anthropic/claude-opus-4-6",
        "fallbacks": [
          "openai-codex/gpt-5.3-codex"
        ]
      },
      "models": {
        "anthropic/claude-opus-4-6": {
          "alias": "opus"
        },
        "openai-codex/gpt-5.3-codex": {}
      }
    }
  }
}
```

- **`primary`** — Default model. Used unless in cooldown.
- **`fallbacks`** — Tried in order when primary is rate-limited.
- **`models`** — Model registry. `alias` allows short names in commands.

## auth-profiles.json Schema

Located at `agents/main/agent/auth-profiles.json`. Managed automatically by the add-profile script and OpenClaw runtime.

```json
{
  "version": 1,
  "profiles": {
    "openai-codex:<name>": {
      "type": "oauth",
      "provider": "openai-codex",
      "access": "<jwt-access-token>",
      "refresh": "<refresh-token>",
      "expires": 1772224563000,
      "accountId": "<openai-account-uuid>"
    }
  },
  "order": {
    "openai-codex": [
      "openai-codex:<account-1>",
      "openai-codex:<account-2>"
    ]
  },
  "lastGood": {
    "openai-codex": "openai-codex:<account-1>"
  },
  "usageStats": {
    "openai-codex:<account-1>": {
      "lastUsed": 1771366450513,
      "errorCount": 0
    }
  }
}
```

Fields:
- **`profiles`** — Live token data per profile
- **`order`** — Failover priority (mirrors openclaw.json)
- **`lastGood`** — Last profile that succeeded per provider
- **`usageStats`** — Auto-tracked. `cooldownUntil` set on rate limit, `errorCount` resets on success

## Cron Job — Model Cooldown Auto-Switch (Optional)

> **This cron job is optional.** It periodically checks for rate-limited providers and automatically switches your active model. When enabled, your model may change without manual intervention. Only add this if you want automatic failover behavior. The job runs only local commands (`openclaw models status`) and writes state to a local file — it does not contact external services.

Add to `cron/jobs.json` in the `jobs` array:

```json
{
  "id": "<generate-uuid>",
  "agentId": "main",
  "name": "model-cooldown-autoswitch",
  "enabled": true,
  "createdAtMs": <now-epoch-ms>,
  "updatedAtMs": <now-epoch-ms>,
  "schedule": {
    "kind": "every",
    "everyMs": 600000,
    "anchorMs": <now-epoch-ms>
  },
  "sessionTarget": "isolated",
  "wakeMode": "now",
  "payload": {
    "kind": "agentTurn",
    "message": "Check model/provider cooldown state and keep main session on an active model.\n\nAvailable auth profiles:\n- anthropic:<your-anthropic-id>\n- openai-codex:<account-1>\n- openai-codex:<account-2>\n\nProcess:\n1) Run `openclaw models status` via exec and parse the output for cooldown markers on each provider/profile.\n2) Pick the best available model based on cooldown state:\n   - Priority: opus (anthropic) > openai-codex/gpt-5.3-codex\n   - If anthropic is in cooldown, try openai-codex profiles (<account-1>, <account-2>)\n   - If all openai-codex profiles are in cooldown too, stay on whichever has the shortest remaining cooldown\n3) Set main session model override accordingly:\n   - use session_status with sessionKey=\"agent:main:main\", model=\"opus\" OR model=\"openai-codex/gpt-5.3-codex\"\n4) Keep a tiny state file at <workspace>/memory/model-switch-state.json with lastEffectiveModel, lastCheckedAt, and profileStatuses. Only send a user-facing message if the effective model changed since last check.\n5) If no change, stay silent.\n\nThis is a periodic model failover monitor/reminder job.",
    "timeoutSeconds": 120
  },
  "delivery": {
    "mode": "none"
  },
  "state": {
    "nextRunAtMs": <now-epoch-ms + 600000>,
    "lastRunAtMs": null,
    "lastStatus": null,
    "lastDurationMs": null,
    "consecutiveErrors": 0
  }
}
```

- **`everyMs: 600000`** — Runs every 10 minutes
- **`sessionTarget: "isolated"`** — Runs in its own session, not the main chat
- **`delivery.mode: "none"`** — Silent unless the job itself sends a message
- Replace `<generate-uuid>` with a real UUID (`uuidgen` or `node -e "console.log(require('crypto').randomUUID())"`)
- Replace `<now-epoch-ms>` with current time in ms (`date +%s000` or `node -e "console.log(Date.now())"`)

- Replace `<workspace>` with your actual workspace path
- List all your auth profile names in the message so the cron agent knows what to check

## Quick Setup Checklist

1. Install codex CLI: `npm i -g @openai/codex`
2. Run `./scripts/codex-add-profile.sh <name>` for each OpenAI account
3. Add profile declarations to `openclaw.json` `auth.profiles` and `auth.order`
4. Add model fallback config to `openclaw.json` `agents.defaults.model`
5. Add the cron job to `cron/jobs.json`
6. Verify: `openclaw models status`


Security scan verdicts — ClawScan: clean | static analysis: clean | VirusTotal: suspicious | SkillSpector: suspicious (HIGH)