Hmbown/CodeWhale · error

unexpected MCP OAuth state while preparing stored credential

Error message

unexpected MCP OAuth state while preparing stored credentials

What it means

When restoring persisted MCP OAuth credentials, a fresh OAuthState is created and set_credentials installs the stored client ID and token response; afterwards the state must have settled into Authorized or Unauthorized to yield a usable manager. Any other enum variant (e.g. a mid-flow state such as Session) means the stored credentials do not map onto a settled state, and construction aborts with this bail.

Source

Thrown at crates/tui/src/mcp/oauth.rs:187

    async fn from_stored_tokens(
        server_name: &str,
        url: &str,
        mut tokens: StoredMcpOAuthTokens,
        default_headers: HeaderMap,
    ) -> Result<Self> {
        refresh_expires_in_from_timestamp(&mut tokens);
        let client = apply_default_headers(crate::tls::reqwest_client_builder(), &default_headers)
            .build()
            .context("building MCP OAuth metadata client")?;
        let mut state = OAuthState::new(url.to_string(), Some(client)).await?;
        state
            .set_credentials(&tokens.client_id, tokens.token_response.0.clone())
            .await
            .context("installing stored MCP OAuth credentials")?;

        let manager = match state {
            OAuthState::Authorized(manager) | OAuthState::Unauthorized(manager) => manager,
            _ => bail!("unexpected MCP OAuth state while preparing stored credentials"),
        };

        Ok(Self {
            inner: Arc::new(McpOAuthRuntimeInner {
                server_name: server_name.to_string(),
                url: url.to_string(),
                manager: Arc::new(Mutex::new(manager)),
                last_tokens: Mutex::new(Some(tokens)),
            }),
        })
    }

    pub async fn authorization_header(&self) -> Result<Option<String>> {
        self.refresh_if_needed().await?;
        let credentials = {
            let guard = self.inner.manager.lock().await;
            let (_client_id, credentials) = guard
                .get_credentials()

View on GitHub (pinned to 0c42157ee5)

Solutions

  1. Clear the stored MCP OAuth credentials for that server (remove the token cache entry)
  2. Re-run the OAuth login flow to write fresh, fully-settled credentials
  3. Verify the stored token JSON matches the current credentials schema if it was migrated between versions

Example fix

# before: stale stored credentials keep OAuthState mid-flow
rm ~/.local/state/codewhale/mcp-oauth/<server>.json  # adjust to your token cache path
codewhale mcp oauth login <server>
Defensive patterns

Strategy: fallback

Try / catch

match McpOAuthRuntime::with_stored_credentials(server_name, url, default_headers, tokens).await {
    Ok(rt) => rt,
    Err(e) if e.to_string().contains("unexpected MCP OAuth state") => {
        // stored credentials are unusable: discard and fall back to a fresh login
        clear_stored_mcp_oauth_tokens(server_name);
        run_oauth_login(server_name, server).await?
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Stored credentials were persisted mid-flow, are from an older format/version whose state transitions differ, or are corrupted such that set_credentials leaves the state machine in a non-settled variant.

Common situations: Upgrading the app with a stale token cache; an interrupted OAuth login leaving partial state; hand-edited or migrated token storage.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20). Data as JSON: /api/errors/cb458db4fe5c5e8c. Report an issue: GitHub.