sigoden/aichat · error · anyhow::Error

Invalid access token

Error message

Invalid access token

What it means

get_access_token looks up a stored access token for the given client name in the ACCESS_TOKENS map; if no entry exists (never authenticated, expired token removed, or wrong client name), it returns 'Invalid access token'. Callers like prepare_chat_completions/prepare_embeddings need this token to authorize API requests.

Solutions

  1. Run the authentication/login flow first so a token is stored for this client
  2. Check that the client_name used for lookup matches the one used when storing the token
  3. Refresh or re-obtain the access token if it expired and was evicted
  4. Verify the provider actually requires/uses access-token auth and your config points at the right client

Example fix

// before
let token = get_access_token("claude")?; // never authenticated
// after
ensure_access_token("claude")?; // login/refresh first
let token = get_access_token("claude")?;
Defensive patterns

Strategy: validation

Validate before calling

// guard before calling APIs that need the token
if !is_valid_access_token("claude") {
    // run login/token-refresh flow first
    refresh_access_token("claude")?;
}
let token = get_access_token("claude")?;

Type guard

fn token_available(client_name: &str) -> bool { is_valid_access_token(client_name) }

Try / catch

let token = match get_access_token(client_name) {
    Ok(t) => t,
    Err(_) => { perform_login(client_name)?; get_access_token(client_name)? }
};

Prevention

When it happens

Trigger: Calling get_access_token (indirectly via prepare_chat_completions/prepare_embeddings) for a client_name that has no entry in ACCESS_TOKENS — token never obtained, was cleared after expiry, or the name is misspelled.

Common situations: Auth/login step was skipped before making chat/embedding calls; access token expired and was not refreshed; client name mismatch between token storage and lookup; using a client that requires OAuth without completing the token exchange.

Related errors


AI-assisted analysis of sigoden/aichat@82976d349a (2026-09-09). Data as JSON: /api/errors/0c258d3d5296bdfc. Report an issue: GitHub.

Appendix: source

Thrown at src/client/access_token.rs:15

use anyhow::{anyhow, Result};
use chrono::Utc;
use indexmap::IndexMap;
use parking_lot::RwLock;
use std::sync::LazyLock;

static ACCESS_TOKENS: LazyLock<RwLock<IndexMap<String, (String, i64)>>> =
    LazyLock::new(|| RwLock::new(IndexMap::new()));

pub fn get_access_token(client_name: &str) -> Result<String> {
    ACCESS_TOKENS
        .read()
        .get(client_name)
        .map(|(token, _)| token.clone())
        .ok_or_else(|| anyhow!("Invalid access token"))
}

pub fn is_valid_access_token(client_name: &str) -> bool {
    let access_tokens = ACCESS_TOKENS.read();
    let (token, expires_at) = match access_tokens.get(client_name) {
        Some(v) => v,
        None => return false,
    };
    !token.is_empty() && Utc::now().timestamp() < *expires_at
}

pub fn set_access_token(client_name: &str, token: String, expires_at: i64) {
    let mut access_tokens = ACCESS_TOKENS.write();
    let entry = access_tokens.entry(client_name.to_string()).or_default();
    entry.0 = token;
    entry.1 = expires_at;
}

View on GitHub (pinned to 82976d349a)