How to Configure Multiple Provider Accounts in jcode

jcode supports concurrent OpenAI and Claude accounts through JSON-backed storage with canonical labeling, enabling instant switching via CLI commands without restarting the session.

Managing multiple AI provider accounts in the jcode CLI requires understanding its three-layer persistence architecture. The system automatically stores credentials in ~/.jcode/ JSON files, generates canonical labels like "openai-1" or "claude-2", and notifies the runtime when you switch active credentials.

Understanding the Multi-Account Architecture

jcode implements multi-account support through three distinct layers:

Layer Responsibility Core Implementation
Account storage Persist JSON files containing account lists and active labels src/auth/codex.rs, src/auth/claude.rs
Generic helpers Normalize labels, generate next label index, switch active accounts src/auth/account_store.rs
Runtime integration Invalidate cached credentials and refresh model catalogs on switch src/server/provider_control.rs, src/tui/app/auth_account_commands.rs

Account Storage and File Locations

OpenAI accounts persist in ~/.jcode/openai-auth.json, while Claude/Anthropic accounts use ~/.jcode/auth.json. Both files store a Vec<Account> where each entry contains OAuth tokens and a label field (e.g., "openai-1").

When first loaded, the relabel_accounts() helper in src/auth/account_store.rs rewrites legacy labels into the canonical prefix-index format:

// src/auth/account_store.rs
pub fn relabel_accounts<T, FGet, FSet>(...) -> RelabelOutcome { ... }

Adding New Provider Accounts

Using the Interactive CLI

Run /account openai add or /account claude add to initiate OAuth authentication. The command invokes upsert_account() from account_store.rs, which:

  1. Assigns the next canonical label via next_account_label() if the incoming account has an empty label
  2. Inserts the account into the stored vector
  3. Sets the account as active if it is the first entry
/account openai add

This opens an inline OAuth prompt. Upon completion, the account appears as openai-2 (or the next available index).

Programmatic Account Creation

You can add accounts programmatically using the provider-specific modules:

// Example: Add a new OpenAI account
let new_account = jcode::auth::codex::OpenAiAccount {
    label: "".to_string(),               // empty → auto-labelled
    access_token: "...".into(),
    refresh_token: "...".into(),
    id_token: None,
    account_id: None,
    expires_at: None,
    email: None,
};
let label = jcode::auth::codex::upsert_account(new_account)?;   // returns "openai-2"

For Claude accounts, use the claude module with AnthropicAccount:

use jcode::auth::claude::{self, AnthropicAccount};

let acct = AnthropicAccount {
    label: "".to_string(),               // auto-label → "claude-1"
    access: "...".into(),
    refresh: "...".into(),
    expires: 0,
    email: Some("user@example.com".into()),
    subscription_type: None,
};

let label = claude::upsert_account(acct)?; // label == "claude-1"

Switching Between Active Accounts

Command-Based Account Switching

Use /account openai switch <label> or the shorthand /:openai <label> to change the active account. The command handler in src/tui/app/auth_account_commands.rs forwards the request to the server:

// src/tui/app/auth_account_commands.rs
AccountCommand::Switch { provider, label } => {
    match provider.as_str() {
        "openai" => remote.switch_openai_account(&label).await?,
        "claude" => remote.switch_anthropic_account(&label).await?,
        ...
    }
}

Server-Side Invalidation Process

When switching, handle_switch_openai_account() in src/server/provider_control.rs performs four critical operations:

  1. Updates the JSON file via crate::auth::codex::set_active_account(&label)
  2. Invalidates cached credentials via provider.invalidate_credentials().await
  3. Clears provider-wide unavailability flags with clear_all_provider_unavailability_for_account
  4. Resets the provider session and triggers a background usage-fetch and model-catalog refresh
// src/server/provider_control.rs
pub(super) async fn handle_switch_openai_account(...) {
    match crate::auth::codex::set_active_account(&label) {
        Ok(()) => {
            crate::auth::AuthStatus::invalidate_cache();
            // ... credential invalidation and cache clearing
        }
        Err(e) => { /* error handling */ }
    }
}

The same flow exists for Claude via handle_switch_anthropic_account.

Listing Configured Accounts

Execute /account (without sub-commands) to open the inline account picker implemented in src/tui/app/auth_account_picker.rs. The UI renders a markdown table built by render_openai_accounts_markdown() and render_claude_accounts_markdown(), displaying each account's label, email, and active status.

/account

This displays sections for OpenAI Accounts and Claude Accounts, clearly indicating which account is currently active.

Handling Token Expiration

When OAuth tokens expire, jcode displays:

OAuth token expired - use `/login openai` to re-authenticate

Run /login openai or /login claude to refresh tokens for the currently active account. Upon successful authentication, the system updates the JSON storage and calls crate::auth::AuthStatus::invalidate_cache() to reload the fresh credentials immediately.

Summary

  • Storage: Accounts persist in ~/.jcode/openai-auth.json and ~/.jcode/auth.json as JSON arrays with canonical labels
  • Adding: Use /account <provider> add CLI commands or programmatic upsert_account() calls in src/auth/account_store.rs
  • Switching: The /account <provider> switch <label> command triggers server-side invalidation via handle_switch_openai_account() or handle_switch_anthropic_account() in src/server/provider_control.rs
  • Runtime: Account changes immediately invalidate cached credentials and refresh model catalogs without requiring a restart
  • Labels: The system automatically generates canonical labels (e.g., "openai-1", "claude-2") using next_account_label() if none is provided

Frequently Asked Questions

How does jcode store multiple accounts for the same provider?

jcode stores multiple accounts in JSON files located at ~/.jcode/openai-auth.json (OpenAI) and ~/.jcode/auth.json (Claude). Each file contains an array of account objects with unique labels in the format {provider}-{index}, managed by the relabel_accounts() function in src/auth/account_store.rs.

Can I switch between OpenAI and Claude accounts without restarting jcode?

Yes. Use /account openai switch <label> or /account claude switch <label> to switch instantly. The server invalidates cached credentials via invalidate_credentials().await and refreshes the model catalog automatically, as implemented in src/server/provider_control.rs.

What happens if I don't specify a label when adding an account?

If you provide an empty label string, the upsert_account() helper in src/auth/account_store.rs automatically generates the next available canonical label using next_account_label(). For example, if "openai-1" exists, the new account becomes "openai-2".

How do I refresh expired tokens for a specific account?

Run /login openai or /login claude to initiate the OAuth flow for the currently active account. Upon successful authentication, the system updates the JSON storage and calls AuthStatus::invalidate_cache() to reload the fresh credentials immediately.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →