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:
- Assigns the next canonical label via
next_account_label()if the incoming account has an empty label - Inserts the account into the stored vector
- 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:
- Updates the JSON file via
crate::auth::codex::set_active_account(&label) - Invalidates cached credentials via
provider.invalidate_credentials().await - Clears provider-wide unavailability flags with
clear_all_provider_unavailability_for_account - 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.jsonand~/.jcode/auth.jsonas JSON arrays with canonical labels - Adding: Use
/account <provider> addCLI commands or programmaticupsert_account()calls insrc/auth/account_store.rs - Switching: The
/account <provider> switch <label>command triggers server-side invalidation viahandle_switch_openai_account()orhandle_switch_anthropic_account()insrc/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →