Skip to content

Conversation

@xycld
Copy link

@xycld xycld commented Jan 26, 2026

需求

将 OAuth 凭证添加自动禁用机制。当收到 401/403 等错误时,将其标记为禁用状态。

配置变更

在配置中新增以下字段:

auto-disable:
  providers:
    codex:
      enabled: true
      status-codes: [401, 403]  # 可选,默认 [401, 403]
      proxy: true               # 代理请求时删除(默认 true)
      management: true          # 管理 API 调用时删除(默认 false)
    claude:
      ...
  • proxy: 控制代理请求的自动删除,默认开启
  • management: 控制管理 API 的自动删除,默认关闭,需要显式开启
    此功能在配置样例默认注释,没有此配置时默认关闭

xycld and others added 5 commits January 26, 2026 12:33
Introduce a flexible auto-delete mechanism that removes OAuth credential
files based on configurable HTTP status codes per provider.

- Add AutoDeleteConfig with per-provider rules and customizable status codes
- Implement TOCTOU-safe deletion with decisionTime validation
- Delete memory state before file to ensure consistency on partial failure
- Add config.example.yaml template for auto-delete settings
Add independent auto-delete configuration for the /api-call endpoint,
allowing management API to automatically delete OAuth credentials when
receiving specific HTTP status codes (default 401/403).

- Add APICallAutoDelete config field reusing AutoDeleteConfig type
- Add tryAPICallAutoDelete() delegating to Manager.RequestAutoDelete()
- Add RequestAutoDelete() public method with proper nil checks
- Export DefaultAutoDeleteStatusCodes constant for reuse
- Fix debug log redundancy in shouldAutoDeleteCredential()
FileSynthesizer.Synthesize() was creating Auth objects without setting
the FileName field. This caused api-call-auto-delete to fail because
tryAPICallAutoDelete checks auth.FileName before proceeding.

The watcher's snapshotCoreAuths() uses FileSynthesizer to create Auth
objects, which then overwrites Manager.auths via AuthUpdate mechanism,
replacing correctly-loaded auths (with FileName) with synthesized auths
(without FileName).

Fix: Set FileName=ID for primary auths and FileName=primary.FileName
for Gemini virtual auths.
Merge api-call-auto-delete into auto-delete config with scope control:
- Add 'proxy' field (default: true) for proxy request auto-delete
- Add 'management' field (default: false) for management API auto-delete
- Remove separate APICallAutoDelete config field

This provides a cleaner, more intuitive configuration:

  auto-delete:
    providers:
      codex:
        enabled: true
        proxy: true       # during proxy requests (default)
        management: true  # during management API calls
Copilot AI review requested due to automatic review settings January 26, 2026 16:25
@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @xycld, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

此拉取请求引入了一项关键功能,旨在提高系统的健壮性和自动化管理能力。通过在检测到 OAuth 凭证失效(例如,收到 401 或 403 HTTP 错误码)时自动禁用这些凭证,系统可以避免持续使用无效凭证进行请求,从而减少错误并优化资源使用。这一改进通过灵活的配置选项,允许管理员根据具体需求为不同服务提供商定制自动禁用策略,确保了在凭证出现问题时能够及时响应并采取纠正措施。

Highlights

  • 新增 OAuth 凭证自动禁用机制: 当 API 返回特定 HTTP 错误码(如 401/403)时,系统将自动把 OAuth 凭证标记为禁用状态,以避免持续使用无效凭证。
  • 引入灵活的配置选项: 新增 auto-disable 配置项,允许用户为不同的服务提供商(如 codex, claude)定制自动禁用规则,包括启用状态、触发的状态码列表以及是否应用于代理请求或管理 API 调用。
  • 管理 API 调用集成: 在管理 API 调用 (/api-call) 失败时,会检查并触发自动禁用逻辑,确保管理操作也能响应凭证失效情况。
  • 凭证管理器功能扩展: 扩展了凭证管理器 (AuthManager),使其能够根据配置规则判断是否需要自动禁用凭证,并处理凭证状态的更新和持久化。
  • 模型注册表更新: 增加了 ModelRegistry 的功能,支持一次性暂停某个凭证关联的所有模型,确保禁用凭证后,所有相关模型也停止服务。

🧠 New Feature in Public Preview: You can now enable Memory to help Gemini Code Assist learn from your team's feedback. This makes future code reviews more consistent and personalized to your project's style. Click here to enable Memory in your admin console.

Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

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

Choose a reason for hiding this comment

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

Code Review

此拉取请求引入了 OAuth 凭证的自动禁用机制,当收到 401/403 等错误时,会自动将凭证标记为禁用状态。此功能通过配置选项进行控制,并已在 API 调用处理程序和认证管理器中实现。代码变更包括添加新的配置结构、更新认证状态管理逻辑以及引入新的辅助函数。总体而言,这是一项有价值的功能,提高了系统的健壮性。

if h == nil || h.cfg == nil || h.authManager == nil {
return
}
if auth == nil || auth.FileName == "" {
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

tryAPICallAutoDisable 函数中,检查 auth.FileName == "" 可能会限制自动禁用功能仅适用于基于文件的 OAuth 凭证。如果存在非文件支持的 OAuth 凭证(例如,通过其他方式注入的运行时凭证),并且它们也需要自动禁用,则此检查可能会阻止它们被处理。请确认这是否是预期行为,即只有文件支持的凭证才应被自动禁用。如果需要支持其他类型的凭证,可能需要调整此条件。

Comment on lines +1226 to +1245
if err := m.persist(ctx, auth); err != nil {
log.WithFields(log.Fields{
"auth_id": auth.ID,
"error": err,
}).Error("failed to persist auth state")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

m.persist(ctx, auth) 的错误处理从 _ = 更改为显式日志记录是一个很好的改进。这提高了代码的可观察性和可维护性,有助于在持久化认证状态失败时快速识别问题。

Suggested change
if err := m.persist(ctx, auth); err != nil {
log.WithFields(log.Fields{
"auth_id": auth.ID,
"error": err,
}).Error("failed to persist auth state")
}
if err := m.persist(ctx, auth); err != nil {
log.WithFields(log.Fields{
"auth_id": auth.ID,
"error": err,
}).Error("failed to persist auth state")
}

Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This pull request implements automatic disabling of OAuth credentials when they receive HTTP 401, 402, or 403 status codes, indicating permanent authentication or authorization failures. The feature is configuration-driven and allows separate control for proxy requests and management API calls.

Changes:

  • Added auto-disable configuration structure with provider-specific rules and separate controls for proxy vs management API paths
  • Implemented auto-disable logic in the proxy request path (MarkResult function) and management API path
  • Added SuspendAllClientModels function to suspend all models for a credential when it's auto-disabled
  • Set FileName field for file-based OAuth credentials to enable file-based credential tracking

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
sdk/cliproxy/auth/conductor.go Implements core auto-disable logic including condition checking, execution in MarkResult for proxy requests, and RequestAutoDisable/executeAutoDisable for management API
internal/api/handlers/management/api_tools.go Adds tryAPICallAutoDisable to handle auto-disable for management API calls
internal/registry/model_registry.go Implements SuspendAllClientModels to suspend all models for a disabled credential
internal/config/config.go Defines AutoDisableConfig and AutoDisableRule structures with validation
config.example.yaml Documents auto-disable configuration options with examples
internal/watcher/synthesizer/file.go Sets FileName field for file-based and virtual auth credentials

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +1176 to +1190
auth.Disabled = true
auth.Status = StatusDisabled
auth.StatusMessage = fmt.Sprintf("auto-disabled: HTTP %d", statusCode)
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

The auth.Status is set to StatusDisabled here when auto-disable conditions are met, but it's unconditionally overwritten to StatusError at line 1218, effectively making the auto-disable status change ineffective. This means the credential will appear as having an error rather than being disabled. To fix this, the code should only set auth.Status to StatusError if the credential was not auto-disabled, or move the auto-disable logic after line 1219 but before line 1220.

Copilot uses AI. Check for mistakes.
autoDisableStatusCode = statusCode
auth.Disabled = true
auth.Status = StatusDisabled
auth.StatusMessage = fmt.Sprintf("auto-disabled: HTTP %d", statusCode)
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

When auto-disabling the credential, the auth.UpdatedAt field should be set to 'now' to maintain consistency with other state updates in this function. Currently, UpdatedAt is only set at line 1219, which happens after the auto-disable logic. Consider setting auth.UpdatedAt = now here along with the other auto-disable state changes, or ensure the code path continues to line 1219 where UpdatedAt is set.

Suggested change
auth.StatusMessage = fmt.Sprintf("auto-disabled: HTTP %d", statusCode)
auth.StatusMessage = fmt.Sprintf("auto-disabled: HTTP %d", statusCode)
auth.UpdatedAt = now

Copilot uses AI. Check for mistakes.
Comment on lines +717 to +729
rule, ok := h.cfg.AutoDisable.Providers[strings.ToLower(auth.Provider)]
if !ok || !rule.Enabled || !rule.Management {
return
}

statusCodes := rule.StatusCodes
if len(statusCodes) == 0 {
statusCodes = coreauth.DefaultAutoDisableStatusCodes
}

if !slices.Contains(statusCodes, statusCode) {
return
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

There's significant code duplication between this function and checkAutoDisableConditionInternal in conductor.go (lines 1456-1476). Both functions check the provider configuration and status codes in nearly identical ways. Consider refactoring to eliminate this duplication - for example, tryAPICallAutoDisable could call checkAutoDisableConditionInternal with forManagement=true to determine whether to proceed with the auto-disable.

Copilot uses AI. Check for mistakes.
Comment on lines +1172 to +1190
if m.checkAutoDisableCondition(auth, statusCode) {
shouldAutoDisable = true
autoDisableAuthID = auth.ID
autoDisableStatusCode = statusCode
auth.Disabled = true
auth.Status = StatusDisabled
auth.StatusMessage = fmt.Sprintf("auto-disabled: HTTP %d", statusCode)
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

The new auto-disable functionality lacks test coverage. Given that this feature automatically disables OAuth credentials based on HTTP error codes, tests should be added to verify: (1) auto-disable triggers correctly for configured status codes and providers, (2) auto-disable respects the proxy vs management configuration flags, (3) already-disabled credentials are not processed again, (4) the status and state changes are persisted correctly, (5) the integration with SuspendAllClientModels works as expected. The codebase has comprehensive test coverage for other features (as seen in files like api_key_model_alias_test.go, oauth_model_alias_test.go, selector_test.go), and this critical feature should follow the same pattern.

Copilot uses AI. Check for mistakes.
Comment on lines +1465 to +1483
} else {
if rule.Proxy != nil && !*rule.Proxy {
return false
}
}
Copy link

Copilot AI Jan 26, 2026

Choose a reason for hiding this comment

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

The default behavior for the Proxy field relies on implicit nil handling. When Proxy is nil (not configured), auto-disable is enabled by default. While this works correctly, consider adding an explicit comment explaining this default-true behavior for better code clarity, or refactoring to make the default more explicit, such as: 'if rule.Proxy == nil || *rule.Proxy' to check if proxy auto-disable should be enabled.

Copilot uses AI. Check for mistakes.
- Rename AutoDeleteConfig -> AutoDisableConfig, auto-delete -> auto-disable
- Change behavior from deleting credentials to disabling them (Disabled=true, Status=StatusDisabled)
- Add SuspendAllClientModels to registry for suspending all models of a client
- Persist disabled state to disk instead of deleting credential files
- Add proper error logging for persist operations
- Merge duplicate case branches (401/402/403) and check functions (DRY)

BREAKING CHANGE: config field renamed from auto-delete to auto-disable
@xycld xycld force-pushed the feature/auto-delete-management-api branch from 4ddf27a to fea80c6 Compare January 26, 2026 17:36
@luispater
Copy link
Collaborator

没有对该功能的支持计划

@luispater luispater closed this Jan 31, 2026
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.

2 participants