From 84e736754699788a234aab6d6d5a5cf0e166fd45 Mon Sep 17 00:00:00 2001 From: wuhaotian2026-svg <339068672+wuhaotian2026-svg@users.noreply.github.com> Date: Sun, 25 Oct 2026 11:11:35 +0800 Subject: [PATCH] fix(poller): fall back to the desktop token when the CLI token is expired --- src/poller/claude.rs | 25 +++++++++++++++++++++---- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/src/poller/claude.rs b/src/poller/claude.rs index d0ef7ef9..959c6f6f 100644 --- a/src/poller/claude.rs +++ b/src/poller/claude.rs @@ -114,14 +114,31 @@ pub(super) fn poll_account(path: &Path) -> Result { let source = credentials.source.clone(); if is_token_expired(credentials.expires_at) { cli_refresh_token(&source); - credentials = read_credentials_from_source(&source).ok_or(PollError::TokenExpired)?; - if is_token_expired(credentials.expires_at) { - return Err(PollError::TokenExpired); - } + credentials = read_credentials_from_source(&source) + .filter(|refreshed| !is_token_expired(refreshed.expires_at)) + .or_else(|| fresh_desktop_credentials_for_default_path(path, &source)) + .ok_or(PollError::TokenExpired)?; } fetch_usage_with_fallback(&credentials.access_token) } +/// A stale CLI token the CLI could not refresh does not end the search for the +/// default profile: the desktop app keeps its own cache fresh while it runs. +fn fresh_desktop_credentials_for_default_path( + path: &Path, + stale: &CredentialSource, +) -> Option { + if matches!(stale, CredentialSource::DesktopApp(_)) { + return None; + } + let credentials = desktop_credentials_for_default_path(path)?; + if is_token_expired(credentials.expires_at) { + return None; + } + diagnose::log("stale CLI token replaced by the Claude desktop app token cache"); + Some(credentials) +} + /// The desktop app's token, but only for a profile that points at the default /// CLI credentials path. fn desktop_credentials_for_default_path(path: &Path) -> Option {