Skip to content

fix: 后台自动刷新纳入 setup-token 账号(修复 8h 后 401)#3883

Merged
Wei-Shaw merged 2 commits into
Wei-Shaw:mainfrom
ShuminFu:fix/setup-token-auto-refresh
Jul 10, 2026
Merged

fix: 后台自动刷新纳入 setup-token 账号(修复 8h 后 401)#3883
Wei-Shaw merged 2 commits into
Wei-Shaw:mainfrom
ShuminFu:fix/setup-token-auto-refresh

Conversation

@ShuminFu

@ShuminFu ShuminFu commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

问题现象

setup-token 类型的 Anthropic 账号在使用约 8 小时后开始返回 401 authentication_error,随后触发 failover。

根因

setup-token 的 access_token 与 oauth 一样是短期令牌——上游返回 expires_in=28800(即 8 小时),并非之前假设的「有效期 1 年」。但后台自动刷新服务故意排除了 setup-token:

  • service/token_refresher.goClaudeTokenRefresher.CanRefresh() 只认 Type == AccountTypeOAuth,注释写着「setup-token 有效期1年,不需要频繁刷新」——与实际的 8h 不符。
  • repository/account_repo.goListOAuthRefreshCandidates 候选查询限定 AND type = 'oauth',同样把 setup-token 排除在外。

于是 setup-token 的 access_token 到期后没有任何人续期,转发路径又直接读库里的 token 照发,Anthropic 返回 401(响应带 request_id,可确认是真打到上游、非本地判过期)。

手动刷新是支持 setup-token 的refreshSingleAccount 的守卫用的是 account.IsOAuth(),它返回 oauth || setup-token。也就是说能力本就具备,只是后台定时路径没走到。

改动

让后台刷新与手动入口保持一致:

  1. ClaudeTokenRefresher.CanRefresh()Type == AccountTypeOAuthaccount.IsOAuth()(涵盖 oauth 与 setup-token),并修正注释。
  2. ListOAuthRefreshCandidates 候选查询:AND type = 'oauth'AND type IN ('oauth', 'setup-token')

为什么安全(不会过度刷新 / 不会打乱现有逻辑)

  • 是否真正刷新仍由 NeedsRefresh 基于 expires_at 门控,只在临近到期时才刷——不是每轮强刷。
  • 刷新在 OAuthRefreshAPI.RefreshIfNeeded分布式锁 + DB 重读 + 竞争恢复下执行,多 worker 并发安全。
  • 候选查询虽对全平台放开,但 setup-token 仅存在于 anthropic;其他平台的 refresher 仍各自用 Type == AccountTypeOAuth 兜底,不受影响。

验证

  • go build ./internal/service/... ./internal/repository/... 通过。
  • 线上以等价方式(定时调用 POST /admin/accounts/:id/refresh)验证过 setup-token 的刷新链路可用:走代理刷新、轮换并存回新 refresh_token、同步 Postgres + Redis 快照,账号有效期回到 ~8h、401 消失。本 PR 把同一能力回归到后台定时路径。

setup-token 的 access_token 同为 8h 短期令牌(expires_in=28800),
此前被后台刷新服务排除,到期后请求返回 401 authentication_error。
放开 CanRefresh 与候选查询的 type='oauth' 限制,与手动刷新入口
(account.IsOAuth()) 保持一致;实际刷新仍由 NeedsRefresh 基于
expires_at 门控并在分布式锁下执行,不会过度刷新。
@github-actions

github-actions Bot commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

All contributors have signed the CLA. ✅
Posted by the CLA Assistant Lite bot.

@ShuminFu

ShuminFu commented Jul 9, 2026

Copy link
Copy Markdown
Contributor Author

I have read the CLA Document and I hereby sign the CLA

github-actions Bot added a commit that referenced this pull request Jul 9, 2026
@XZB-1248

XZB-1248 commented Jul 9, 2026

Copy link
Copy Markdown

我终于知道为什么动不动就给我报credential invalid了😭
每隔一段时间我就要手动刷新,麻烦死了

- ListOAuthRefreshCandidates SQL 断言由 type = 'oauth' 改为
  type IN ('oauth', 'setup-token')。
- ClaudeTokenRefresher.CanRefresh 增加 anthropic setup-token → true 用例。
@ShuminFu

Copy link
Copy Markdown
Contributor Author

已修复此前 CI 单测失败:

TestAccountRepository_ListOAuthRefreshCandidates_SQLFilter 硬断言 SQL 含 type = oauth,本 PR 把该条件改为 type IN (oauth, setup-token) 后断言过时。已同步更新:

  • 该断言改为 type IN (oauth, setup-token)
  • TestClaudeTokenRefresher_CanRefresh 增加 anthropic setup-token → true 用例,锁定本次行为变更。

本地已用 go test -tags=unit ./internal/repository/... ./internal/service/... 验证通过。

当前 CI/Security Scan 显示 action_required(首次外部贡献者的 workflow 需人工批准),烦请 approve 重跑,谢谢!

@Wei-Shaw
Wei-Shaw merged commit 6a2063b into Wei-Shaw:main Jul 10, 2026
7 checks passed
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 10, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants