Skip to content

fix: clear quota cooldown after capacity changes#3809

Open
dhw1111 wants to merge 1 commit into
router-for-me:devfrom
dhw1111:feature/fix-quota-cooldown-upgrade
Open

fix: clear quota cooldown after capacity changes#3809
dhw1111 wants to merge 1 commit into
router-for-me:devfrom
dhw1111:feature/fix-quota-cooldown-upgrade

Conversation

@dhw1111

@dhw1111 dhw1111 commented Jun 11, 2026

Copy link
Copy Markdown

概述

修复账号容量相关信息变化后,旧的 quota 冷却状态仍然被继承的问题。

当 Codex 账号触发额度限制时,CPA 会保存按模型维度的运行时状态,例如 Quota.ExceededNextRetryAfter。如果后续账号因为订阅升级、订阅变更或容量变化而刷新,旧的 ModelStates 可能仍然被继承,导致账号明明已经有新额度,却依然被旧冷却状态阻塞。

这个修改会在账号容量相关元数据没有变化时才继承旧的 ModelStates

修改内容

  • 新增 ShouldInheritModelStates,统一判断 auth 更新时是否应该保留旧的模型运行时状态。
  • 当 plan、subscription、quota、tier 等容量相关元数据变化时,清理旧的按模型冷却状态。
  • 模型注册刷新路径也使用同一套继承判断逻辑。
  • Codex 本地 token refresh 时,从新的 ID token 中同步容量相关 claims。
  • 添加回归测试,覆盖订阅变化时清理旧状态、订阅未变化时保留旧状态两个场景。

测试

  • go test ./...

@github-actions

Copy link
Copy Markdown

This pull request targeted main.

The base branch has been automatically changed to dev.

@github-actions github-actions Bot changed the base branch from main to dev June 11, 2026 16:41

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 215879edcd

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/conductor.go Outdated
Comment on lines +1439 to +1442
if len(incoming.ModelStates) > 0 || len(existing.ModelStates) == 0 {
return false
}
return capacityIdentitySignatureEqual(authCapacityIdentitySignature(existing), authCapacityIdentitySignature(incoming))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Drop cloned model states when capacity changes

When the scheduler refreshes an auth, refreshAuth clones the current auth before calling the executor, so updated already contains the old ModelStates; Codex then only mutates capacity claims on that clone. Because this early return skips all signature comparison whenever incoming.ModelStates is non-empty, Update stores the stale cloned cooldowns even if plan_type or subscription metadata changed, so the main local token-refresh path still leaves upgraded Codex accounts blocked until the old quota retry expires.

Useful? React with 👍 / 👎.

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces a mechanism to clear stale quota cooldowns and model states when an account's capacity identity changes (such as a Codex plan upgrade), allowing immediate retries with new limits. It adds token parsing to extract ChatGPT subscription details and updates the auth update flow to compare capacity signatures. The review feedback highlights two critical issues: a potential nil pointer dereference and stale claims retention in applyCodexCapacityClaims, and a type mismatch issue during token refresh in capacityIdentityValue when handling pointer types, which would prevent model states from being correctly inherited.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +1414 to +1446
func applyCodexCapacityClaims(auth *cliproxyauth.Auth, idToken string) {
if auth == nil || strings.TrimSpace(idToken) == "" {
return
}
claims, errParse := codexauth.ParseJWTToken(idToken)
if errParse != nil || claims == nil {
if errParse != nil {
log.Warnf("codex executor: failed to parse refreshed capacity claims: %v", errParse)
}
return
}

info := claims.CodexAuthInfo
if auth.Attributes == nil {
auth.Attributes = make(map[string]string)
}
if auth.Metadata == nil {
auth.Metadata = make(map[string]any)
}
if planType := strings.TrimSpace(info.ChatgptPlanType); planType != "" {
auth.Attributes["plan_type"] = planType
auth.Metadata["chatgpt_plan_type"] = planType
}
if accountID := strings.TrimSpace(info.ChatgptAccountID); accountID != "" {
auth.Metadata["chatgpt_account_id"] = accountID
}
if info.ChatgptSubscriptionActiveStart != nil {
auth.Metadata["chatgpt_subscription_active_start"] = info.ChatgptSubscriptionActiveStart
}
if info.ChatgptSubscriptionActiveUntil != nil {
auth.Metadata["chatgpt_subscription_active_until"] = info.ChatgptSubscriptionActiveUntil
}
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

Potential Panic & Stale Capacity Claims Bug

  1. Potential Nil Pointer Dereference: If claims.CodexAuthInfo is a pointer type, it can be nil if the corresponding claim is missing from the JWT. Accessing fields on it without a nil check would cause a panic.
  2. Stale Capacity Claims: If a claim (like ChatgptPlanType or ChatgptSubscriptionActiveUntil) becomes empty or nil (e.g., subscription expired or downgraded), the current code does not clear the old value from auth.Attributes or auth.Metadata because of the if ... != "" and if ... != nil guards. This keeps the expired subscription/plan active in the auth state, preventing the quota cooldown from being cleared when it should be.

We should add a nil check for info and explicitly clear/delete attributes and metadata keys if they are empty or nil in the new token.

func applyCodexCapacityClaims(auth *cliproxyauth.Auth, idToken string) {
	if auth == nil || strings.TrimSpace(idToken) == "" {
		return
	}
	claims, errParse := codexauth.ParseJWTToken(idToken)
	if errParse != nil || claims == nil {
		if errParse != nil {
			log.Warnf("codex executor: failed to parse refreshed capacity claims: %v", errParse)
		}
		return
	}

	info := claims.CodexAuthInfo
	if info == nil {
		return
	}

	if auth.Attributes == nil {
		auth.Attributes = make(map[string]string)
	}
	if auth.Metadata == nil {
		auth.Metadata = make(map[string]any)
	}

	if planType := strings.TrimSpace(info.ChatgptPlanType); planType != "" {
		auth.Attributes["plan_type"] = planType
		auth.Metadata["chatgpt_plan_type"] = planType
	} else {
		delete(auth.Attributes, "plan_type")
		delete(auth.Metadata, "chatgpt_plan_type")
	}

	if accountID := strings.TrimSpace(info.ChatgptAccountID); accountID != "" {
		auth.Metadata["chatgpt_account_id"] = accountID
	} else {
		delete(auth.Metadata, "chatgpt_account_id")
	}

	if info.ChatgptSubscriptionActiveStart != nil {
		auth.Metadata["chatgpt_subscription_active_start"] = info.ChatgptSubscriptionActiveStart
	} else {
		delete(auth.Metadata, "chatgpt_subscription_active_start")
	}

	if info.ChatgptSubscriptionActiveUntil != nil {
		auth.Metadata["chatgpt_subscription_active_until"] = info.ChatgptSubscriptionActiveUntil
	} else {
		delete(auth.Metadata, "chatgpt_subscription_active_until")
	}
}

Comment thread sdk/cliproxy/auth/conductor.go
@dhw1111 dhw1111 force-pushed the feature/fix-quota-cooldown-upgrade branch from 215879e to ee1bb38 Compare June 11, 2026 16:51

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee1bb38848

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

}
if planType := strings.TrimSpace(info.ChatgptPlanType); planType != "" {
auth.Attributes["plan_type"] = planType
auth.Metadata["chatgpt_plan_type"] = planType

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve cooldowns when refreshing the same Codex plan

When a Codex auth was created by the device flow, buildAuthRecord stores the plan only in Attributes["plan_type"] and the metadata initially contains just email. On the next successful refresh with the same plan, this line adds a new meta:chatgpt_plan_type entry to the capacity signature even though attr:plan_type is unchanged; because Update compares attr and meta keys separately, shouldClearModelStates treats this as a capacity change and drops any cloned per-model quota cooldown. In practice, an account that is still rate-limited can become immediately eligible again after its first token refresh purely because duplicate metadata was backfilled, not because the plan changed.

Useful? React with 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: ee1bb38848

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread sdk/cliproxy/auth/conductor.go Outdated
Comment on lines +1579 to +1580
} else if shouldClearModelStates(existing, auth) {
auth.ModelStates = nil

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Clear aggregate cooldown when dropping model states

When a refreshed auth is a clone of a quota-cooled account and its capacity signature changes, this branch removes ModelStates but leaves the cloned auth-level fields (Unavailable, NextRetryAfter, Quota, and StatusMessage) untouched. For accounts where all model states had contributed to the aggregate cooldown, management output and any selection path that calls isAuthBlockedForModel with an empty model will still treat the account as cooling down until the old retry time, so the capacity-change refresh only partially clears the stale quota state. Clear/recompute the aggregate availability when dropping the per-model map.

Useful? React with 👍 / 👎.

@dhw1111 dhw1111 force-pushed the feature/fix-quota-cooldown-upgrade branch from ee1bb38 to 4674f09 Compare June 11, 2026 17:00

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 4674f097cb

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +1437 to +1438
delete(auth.Attributes, "plan_type")
delete(auth.Metadata, "chatgpt_plan_type")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Preserve Codex plan when refresh omits the claim

When a valid refreshed Codex ID token omits chatgpt_plan_type, this branch deletes the existing Attributes["plan_type"]. I checked the downstream routing/model registration paths: sdk/cliproxy/service.go defaults an empty Codex plan to the Pro model set, and isFreeCodexAuth only blocks credentials whose plan is explicitly "free"; therefore a free account whose refresh response temporarily lacks this claim loses its free marker and can be registered/selected as an unrestricted Pro-capable account. Absence of the claim is not enough evidence to upgrade capacity, so keep the previous plan or mark it conservatively instead of deleting it.

Useful? React with 👍 / 👎.

@dhw1111 dhw1111 force-pushed the feature/fix-quota-cooldown-upgrade branch from 4674f09 to cdd691f Compare June 11, 2026 17:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant