From 33aa665555e4ea6e2687cb2d6355d39f6fe5df72 Mon Sep 17 00:00:00 2001 From: Zhi Yang <196515526+FakerL@users.noreply.github.com> Date: Mon, 5 Jan 2026 10:21:29 +0000 Subject: [PATCH 1/2] fix(auth): persist access_token on refresh for providers that need it MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Previously, metadataEqualIgnoringTimestamps() ignored access_token for all providers, which prevented refreshed tokens from being persisted to disk/database. This caused tokens to be lost on server restart for providers like iFlow. This change makes the behavior provider-specific: - Providers like gemini/gemini-cli that issue new tokens on every refresh and can re-fetch when needed will continue to ignore access_token (optimization) - Other providers like iFlow will now persist access_token changes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude --- sdk/auth/filestore.go | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/sdk/auth/filestore.go b/sdk/auth/filestore.go index 84092d37..772de9d6 100644 --- a/sdk/auth/filestore.go +++ b/sdk/auth/filestore.go @@ -74,7 +74,7 @@ func (s *FileTokenStore) Save(ctx context.Context, auth *cliproxyauth.Auth) (str if existing, errRead := os.ReadFile(path); errRead == nil { // Use metadataEqualIgnoringTimestamps to skip writes when only timestamp fields change. // This prevents the token refresh loop caused by timestamp/expired/expires_in changes. - if metadataEqualIgnoringTimestamps(existing, raw) { + if metadataEqualIgnoringTimestamps(existing, raw, auth.Provider) { return path, nil } } else if errRead != nil && !os.IsNotExist(errRead) { @@ -284,7 +284,10 @@ func jsonEqual(a, b []byte) bool { // ignoring fields that change on every refresh but don't affect functionality. // This prevents unnecessary file writes that would trigger watcher events and // create refresh loops. -func metadataEqualIgnoringTimestamps(a, b []byte) bool { +// The provider parameter controls whether access_token is ignored: providers like +// Google OAuth (gemini, gemini-cli) can re-fetch tokens when needed, while others +// like iFlow require the refreshed token to be persisted. +func metadataEqualIgnoringTimestamps(a, b []byte, provider string) bool { var objA, objB map[string]any if err := json.Unmarshal(a, &objA); err != nil { return false @@ -295,9 +298,18 @@ func metadataEqualIgnoringTimestamps(a, b []byte) bool { // Fields to ignore: these change on every refresh but don't affect authentication logic. // - timestamp, expired, expires_in, last_refresh: time-related fields that change on refresh - // - access_token: Google OAuth returns a new access_token on each refresh, this is expected - // and shouldn't trigger file writes (the new token will be fetched again when needed) - ignoredFields := []string{"timestamp", "expired", "expires_in", "last_refresh", "access_token"} + ignoredFields := []string{"timestamp", "expired", "expires_in", "last_refresh"} + + // Providers that issue new access_token on every refresh and can re-fetch when needed. + // For these providers, we also ignore access_token to avoid unnecessary file writes. + providersIgnoringAccessToken := map[string]bool{ + "gemini": true, + "gemini-cli": true, + } + if providersIgnoringAccessToken[provider] { + ignoredFields = append(ignoredFields, "access_token") + } + for _, field := range ignoredFields { delete(objA, field) delete(objB, field) From 08d21b76e2ba40d522673b963b3559b405dd3f50 Mon Sep 17 00:00:00 2001 From: FakerL Date: Mon, 5 Jan 2026 21:38:26 +0800 Subject: [PATCH 2/2] Update sdk/auth/filestore.go Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com> --- sdk/auth/filestore.go | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sdk/auth/filestore.go b/sdk/auth/filestore.go index 772de9d6..3dd9f752 100644 --- a/sdk/auth/filestore.go +++ b/sdk/auth/filestore.go @@ -300,13 +300,10 @@ func metadataEqualIgnoringTimestamps(a, b []byte, provider string) bool { // - timestamp, expired, expires_in, last_refresh: time-related fields that change on refresh ignoredFields := []string{"timestamp", "expired", "expires_in", "last_refresh"} - // Providers that issue new access_token on every refresh and can re-fetch when needed. - // For these providers, we also ignore access_token to avoid unnecessary file writes. - providersIgnoringAccessToken := map[string]bool{ - "gemini": true, - "gemini-cli": true, - } - if providersIgnoringAccessToken[provider] { + // For providers that can re-fetch tokens when needed (e.g., Google OAuth), + // we ignore access_token to avoid unnecessary file writes. + switch provider { + case "gemini", "gemini-cli": ignoredFields = append(ignoredFields, "access_token") }