Skip to content

fix(proxy): 将 messages 中的 role:"system" 消息合并到顶层 system 字段,修复严格上游 400#1268

Open
00010110 wants to merge 3 commits into
ding113:devfrom
00010110:fix/system-role-message-rectifier
Open

fix(proxy): 将 messages 中的 role:"system" 消息合并到顶层 system 字段,修复严格上游 400#1268
00010110 wants to merge 3 commits into
ding113:devfrom
00010110:fix/system-role-message-rectifier

Conversation

@00010110

@00010110 00010110 commented Jun 11, 2026

Copy link
Copy Markdown

背景

Fixes #1265

Claude Code 2.1.172+(2.1.153 之后引入的行为变化)配合自定义模型名(ANTHROPIC_DEFAULT_OPUS_MODEL 等指向 cchub 渠道模型)使用时,会在 messages 数组中注入独立的 {"role": "system", "content": "..."} 消息(skills 列表)。Anthropic 官方 API 接受该写法,但 Vertex/Bedrock 类严格校验 messages 角色的兼容上游会以 400 拒绝,cchub 原样透传导致重试耗尽全部供应商后熔断,客户端收到 503「所有供应商暂时不可用」。

修复方案

参照现有 billing-header-rectifier.ts 的 proactive rectifier 模式:

核心模块 src/app/v1/_lib/proxy/system-message-rectifier.ts

  • 转发前扫描 messages,取出 role: "system" 条目
  • string content 转为 {type: "text", text} block;数组 content 保留 text block(含 cache_control 等附加字段),丢弃非 text block
  • 追加到顶层 system 字段末尾(顶层为 string 时先转为首个 text block)
  • 原地变异,语义与 Anthropic 官方 API 对该写法的处理一致:宽容上游无行为变化,严格上游消除 400

forwarder 集成

  • 仅对 claude / claude-auth 供应商生效
  • 置于 billing header rectifier 之前,确保 system 消息内若携带 x-anthropic-billing-header 文本块,搬移后仍会被后续剥离
  • 命中时记录 system_message_rectifier specialSetting 审计并写日志

系统设置开关 enableSystemMessageRectifier(默认开启)

  • 全链路:schema 新列 + 迁移(drizzle/0105_easy_cyclops.sql)、settings cache、types、zod 校验、Server Action、admin REST、v1 OpenAPI schema、repository、transformers
  • 按既有模式接入 system_settings 缺列降级链(读取与更新路径均在链头新增一层剔除该列的降级),迁移前部署的实例读取/更新设置不受影响
  • 设置页新增开关项,补全 zh-CN / zh-TW / en / ja / ru 五语言文案

测试

新增 tests/unit/proxy/system-message-rectifier.test.ts,16 个用例:

  • string / array content、空 content、非法 content
  • 顶层 system 为 undefined / string / array / 空串 / 未知类型
  • 多条 system 消息保序、messages 含 null / 非对象条目
  • 原地变异引用不变
  • 与 billing rectifier 按 forwarder 实际顺序的组合场景(system 消息内携带 billing header 块最终被剥离)

验证

  • bun run build 通过
  • bun run lint / lint:fix 通过
  • bun run typecheck 通过
  • bun run test 全量 6370 个测试通过
  • bun run openapi:check 通过

Greptile Summary

This PR adds a proactive rectifier that lifts role:"system" messages out of the messages array and merges them into the top-level system field before forwarding, matching the lenient interpretation the official Anthropic API already applies and removing the source of 400 errors on strict upstreams like Vertex/Bedrock.

  • Core rectifier (system-message-rectifier.ts): synchronous, in-place mutation; correctly handles all content shapes (string → single block, array → filter to text blocks, empty string → no block, unknown type → discard), and all top-level system shapes (undefined, null, string, array, unknown).
  • Forwarder integration: runs before the billing-header rectifier (so billing blocks moved via this pass are still stripped), guarded by enableSystemMessageRectifier setting; both rectifiers share the single getCachedSystemSettings() result fetched once per doForward call.
  • Settings plumbing: full migration, cache, schema, zod, admin API, and admin UI coverage; column-missing fallback chain prepends the new column to the existing degradation ladder.

Confidence Score: 5/5

Safe to merge. The rectifier is synchronous, idempotent, and only mutates messages when role:"system" entries are present — requests without them pass through unchanged.

The change is a well-bounded proactive transformation applied only to claude/claude-auth providers, guarded by a feature flag that defaults to the safe/on state. The core function handles all edge cases and is tested with 16 targeted unit tests. Settings plumbing follows the established pattern with the new column correctly positioned as the outermost entry in the degradation ladder.

No files require special attention.

Important Files Changed

Filename Overview
src/app/v1/_lib/proxy/system-message-rectifier.ts New core rectifier module — well-typed, mutates in place, handles all content/system field shapes correctly; comprehensive unit tests.
src/app/v1/_lib/proxy/forwarder.ts Integrates the new rectifier before the billing-header rectifier, both sharing a single getCachedSystemSettings() result. Logic and ordering are correct.
src/repository/system-config.ts Adds enableSystemMessageRectifier to both full-select and fallback ladder (as the outermost/newest fallback step), and to the update path fallback; consistent with prior pattern.
drizzle/0106_stiff_dormammu.sql Single-column migration adding enable_system_message_rectifier boolean NOT NULL DEFAULT true — safe, additive, non-blocking.
tests/unit/proxy/system-message-rectifier.test.ts 16 well-chosen unit tests covering all content shapes, system field shapes, ordering, in-place mutation, and the combined billing-header interaction.
src/types/special-settings.ts Adds SystemMessageRectifierSpecialSetting type; consistent shape with BillingHeaderRectifierSpecialSetting.
src/lib/config/system-settings-cache.ts Adds enableSystemMessageRectifier: true to the DEFAULT_SETTINGS fallback and to the cold-cache fallback object; consistent with other rectifier defaults.
tests/unit/repository/system-config-update-missing-columns.test.ts Existing test updated with the new fallback path; the test mock for the full-column set does not include the new enableSystemMessageRectifier field (minor gap, but not blocking).

Sequence Diagram

sequenceDiagram
    participant CC as Claude Code Client
    participant FW as ProxyForwarder.doForward
    participant SMR as SystemMessageRectifier
    participant BHR as BillingHeaderRectifier
    participant UP as Upstream (Vertex/Bedrock)

    CC->>FW: POST /v1/messages
    FW->>FW: getCachedSystemSettings()
    alt "enableSystemMessageRectifier = true"
        FW->>SMR: rectifySystemMessages(message)
        SMR-->>FW: "applied=true, messages cleaned, system merged"
    end
    alt "enableBillingHeaderRectifier = true"
        FW->>BHR: rectifyBillingHeader(message)
        BHR-->>FW: billing header blocks stripped
    end
    FW->>UP: POST with clean messages array
    UP-->>FW: 200 OK
    FW-->>CC: 200 OK
Loading

Comments Outside Diff (1)

  1. src/app/v1/_lib/proxy/forwarder.ts, line 2480-2483 (link)

    P2 Two consecutive getCachedSystemSettings() calls are made in the same code path — one for the system message rectifier and one for the billing header rectifier. Since the cache returns the same in-memory object within the 1-minute TTL, the second call is a no-op but still adds an unnecessary await. Both rectifiers could share the single settings object that was already fetched.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: src/app/v1/_lib/proxy/forwarder.ts
    Line: 2480-2483
    
    Comment:
    Two consecutive `getCachedSystemSettings()` calls are made in the same code path — one for the system message rectifier and one for the billing header rectifier. Since the cache returns the same in-memory object within the 1-minute TTL, the second call is a no-op but still adds an unnecessary `await`. Both rectifiers could share the single `settings` object that was already fetched.
    
    
    
    How can I resolve this? If you propose a fix, please make it concise.

    Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Reviews (3): Last reviewed commit: "merge: resolve conflicts with dev (PR #1..." | Re-trigger Greptile

…ing113#1265)

Claude Code 2.1.172+ 配合自定义模型名时会在 messages 数组中注入独立的
role:"system" 消息(skills 列表)。Anthropic 官方 API 接受该写法,但
Vertex/Bedrock 类严格校验的兼容上游会以 400 拒绝,导致 cchub 重试耗尽
全部供应商后熔断返回 503。

- 新增 system message rectifier(proactive,参照 billing-header-rectifier
  模式):转发前将 role:"system" 消息从 messages 中取出,文本内容以
  {type:"text",text} block 形式追加到顶层 system 字段末尾,原地变异并
  记录 system_message_rectifier specialSetting 审计
- 在 forwarder 中对 claude/claude-auth 供应商启用,且置于 billing header
  rectifier 之前,确保 system 消息内携带的 billing header 块在搬移后仍
  会被剥离
- 新增系统设置开关 enableSystemMessageRectifier(默认开启),覆盖
  schema/迁移/缓存/类型/校验/Server Action/REST/OpenAPI/UI 全链路,
  并按既有模式接入 system_settings 缺列降级链(读取与更新),保证
  迁移前部署的实例行为不回退
- 设置页新增开关项,补全 zh-CN/zh-TW/en/ja/ru 五语言文案
- 新增 16 个单元测试覆盖 string/array content、顶层 system 各形态、
  保序、原地变异及与 billing rectifier 的执行顺序组合

Fixes ding113#1265
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

本 PR 实现系统消息整流器功能。当 Claude Code 2.1.172+ 使用自定义模型名时,会在 messages 数组中注入 role: "system" 的消息,导致严格校验的 Anthropic 兼容上游(Vertex/Bedrock 等)拒绝。整流器在转发前自动抽取这些消息并合并到顶层 system 字段,消除 400 错误。

变更说明

System Message Rectifier 整体实现

Layer / File(s) Summary
数据库迁移与类型契约
drizzle/0106_stiff_dormammu.sql, drizzle/meta/_journal.json, src/drizzle/schema.ts, src/types/system-config.ts, src/lib/api/v1/schemas/system-config.ts, src/lib/api-client/v1/openapi-types.gen.ts, src/lib/validation/schemas.ts
新增数据库迁移添加 enable_system_message_rectifier 列(默认 true),日志记录迁移版本;在 schema、类型、生成 API 类型、validation schema 中声明布尔字段 enableSystemMessageRectifier
默认值、缓存与数据转换
src/lib/config/system-settings-cache.ts, src/repository/_shared/transformers.ts
系统设置缓存默认值补充 enableSystemMessageRectifier: truetoSystemSettings transformer 在返回对象中合并该字段,缺失时默认为 true。
Repository 读取更新与缺列降级兼容
src/repository/system-config.ts
getSystemSettings()updateSystemSettings() 的读取更新路径中包含新字段;数据库缺列时分层降级,最先移除该字段后重试,然后依次降级到已有字段集。
国际化文案与管理端 API
messages/{en,ja,ru,zh-CN,zh-TW}/settings/config.json, src/app/api/admin/system-config/route.ts
5 种语言配置文件新增 enableSystemMessageRectifierenableSystemMessageRectifierDesc 文案;POST 路由从校验结果映射该字段到 updateSystemSettings 入参。
设置表单、页面与服务端操作
src/actions/system-config.ts, src/app/[locale]/settings/config/page.tsx, src/app/[locale]/settings/config/_components/system-settings-form.tsx
客户端操作函数入参、设置页面初始化、表单组件新增该字段的状态管理、表单提交与保存成功后的回填逻辑;UI 新增 Switch 控件用于用户启用/禁用整流器。
整流器核心实现与审计类型
src/app/v1/_lib/proxy/system-message-rectifier.ts, src/types/special-settings.ts
新增 rectifySystemMessages() 函数从 messages 中抽取 system 条目、规范化其内容为文本块、合并到顶层 system;定义 SystemMessageRectifierSpecialSetting 类型用于审计 applied/movedCount/extractedValues
Forwarder 转发链路集成与审计键
src/app/v1/_lib/proxy/forwarder.ts, src/lib/utils/special-settings.ts
Claude/Claude-auth provider 的覆写逻辑中条件执行整流器(在 billing header rectifier 之前),命中后构建并持久化 special setting;buildSettingKey() 新增 system_message_rectifier 分支用于去重。
整流器与缺列降级测试覆盖
tests/unit/proxy/system-message-rectifier.test.ts, tests/unit/repository/system-config-update-missing-columns.test.ts
新增 13 个单元测试覆盖整流器的常规迁移、边界情况、原地引用语义与 billing header 联动;新增 3 个 repository 缺列降级测试验证分层移除字段的逻辑。

预估代码审查工作量

🎯 3 (Moderate) | ⏱️ ~25 minutes

可能相关的 PR

  • ding113/claude-code-hub#576: 同样在 ProxyForwarder.doForward 中为不同的 rectifier(thinking_signature_rectifier vs system_message_rectifier)添加请求改写与特殊设置持久化逻辑。
  • ding113/claude-code-hub#784: System message rectifier 作为额外的转发前处理步骤集成在 forwarder.ts 中,直接扩展现有的 billing header rectifier 工作。
  • ding113/claude-code-hub#979: 新增 system_message_rectifier 特殊设置并通过 persistSpecialSettings(session) 持久化,与该 PR 在相同的特殊设置持久化路径上重叠。
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed PR 标题清晰准确地概括了主要变更:将 messages 中的 role:"system" 消息合并到顶层 system 字段以修复严格上游 400 错误。
Linked Issues check ✅ Passed PR 完整实现了 Issue #1265 的所有编码需求:系统消息整流器核心逻辑、forwarder 集成、系统设置开关全链路、16 个单元测试、向后兼容的缺列降级链。
Out of Scope Changes check ✅ Passed 所有变更都在 Issue #1265 指定的修复范围内,包括核心整流器、forwarder 集成、系统设置、数据库迁移、UI 文案和测试,无超出范围的改动。
Description check ✅ Passed PR 描述与变更集高度相关,详细说明了背景问题(Claude Code 2.1.172+ 注入 system 消息导致严格上游 400)、修复方案(system-message-rectifier 核心模块)、系统设置开关、测试覆盖与验证。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@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 System Message Rectifier to handle cases where the Claude Code client (v2.1.172+) injects 'role: system' messages directly into the 'messages' array, which causes 400 errors on strict Anthropic-compatible upstreams like Vertex and Bedrock. The rectifier extracts these system messages and merges them into the top-level 'system' field. The changes span database migrations, schema updates, UI toggle settings, localization, and comprehensive unit tests for the new rectifier logic. There are no review comments, and I have no feedback to provide.

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.

@github-actions github-actions Bot added the size/XL Extra Large PR (> 1000 lines) label Jun 11, 2026

@github-actions github-actions 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 Summary

No significant issues identified. The core rectifier logic is correct, well-tested (16 cases), and follows the established billing-header-rectifier pattern exactly. The degradation chain, type definitions, settings propagation, and i18n are all consistent with existing conventions.

PR Size: XL

  • Lines changed: 5,137 (5,121 additions + 16 deletions)
  • Files changed: 25

Note: ~4,535 lines are from the auto-generated Drizzle migration snapshot. Meaningful code changes are ~600 lines across the new rectifier module, forwarder integration, repository degradation chain, settings plumbing, and tests.

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 0 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 0 0 0
Simplification 0 0 0 0

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean
  • Type safety - Clean
  • Documentation accuracy - Clean
  • Test coverage - Comprehensive (16 test cases covering string/array content, empty content, non-object entries, mutation semantics, integration with billing rectifier)
  • Code clarity - Good

Automated review by Claude AI

@coderabbitai coderabbitai 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.

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/app/v1/_lib/proxy/forwarder.ts`:
- Line 95: The new import uses a relative path; update the import of
rectifySystemMessages in forwarder.ts to use the project path alias that maps
./src to `@/` (e.g. replace "./system-message-rectifier" with the corresponding
"`@/`..." module path), so the import reads from the `@/` alias and conforms to the
repository import rules.

In `@src/app/v1/_lib/proxy/system-message-rectifier.ts`:
- Around line 25-29: The NOOP_RESULT constant shares a single extractedValues
array reference which causes callers that mutate it to leak state; replace the
shared constant with a factory that returns a fresh SystemMessageRectifierResult
each time (e.g. createNoopResult(): SystemMessageRectifierResult { return {
applied:false, movedCount:0, extractedValues:[] }; }) and update all places that
currently do shallow copies like { ...NOOP_RESULT } to call createNoopResult()
(also fix the other similar NOOP-style returns referenced around the other
occurrences so each call gets its own extractedValues array).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: ddd59d5f-5e0a-4ae2-90cf-d6d1ae0b6659

📥 Commits

Reviewing files that changed from the base of the PR and between 499d925 and b61e1d7.

📒 Files selected for processing (25)
  • drizzle/0105_easy_cyclops.sql
  • drizzle/meta/0105_snapshot.json
  • drizzle/meta/_journal.json
  • messages/en/settings/config.json
  • messages/ja/settings/config.json
  • messages/ru/settings/config.json
  • messages/zh-CN/settings/config.json
  • messages/zh-TW/settings/config.json
  • src/actions/system-config.ts
  • src/app/[locale]/settings/config/_components/system-settings-form.tsx
  • src/app/[locale]/settings/config/page.tsx
  • src/app/api/admin/system-config/route.ts
  • src/app/v1/_lib/proxy/forwarder.ts
  • src/app/v1/_lib/proxy/system-message-rectifier.ts
  • src/drizzle/schema.ts
  • src/lib/api-client/v1/openapi-types.gen.ts
  • src/lib/api/v1/schemas/system-config.ts
  • src/lib/config/system-settings-cache.ts
  • src/lib/utils/special-settings.ts
  • src/lib/validation/schemas.ts
  • src/repository/_shared/transformers.ts
  • src/repository/system-config.ts
  • src/types/special-settings.ts
  • src/types/system-config.ts
  • tests/unit/proxy/system-message-rectifier.test.ts

Comment thread src/app/v1/_lib/proxy/forwarder.ts
Comment thread src/app/v1/_lib/proxy/system-message-rectifier.ts Outdated
- system-message-rectifier: NOOP_RESULT 常量浅拷贝会共享 extractedValues 数组引用,改为 createNoopResult() 工厂函数,每次返回独立数组
- forwarder: 两个 rectifier 块共用一次 getCachedSystemSettings() 调用,消除冗余 await

回应 PR ding113#1268 的 CodeRabbit 与 Greptile 审查意见
@00010110

Copy link
Copy Markdown
Author

已根据审查意见更新(1d2bd1a8):

  • CodeRabbit – NOOP_RESULT 共享数组引用:已修复,改为 createNoopResult() 工厂函数,每次返回独立的 extractedValues 数组。
  • Greptile – 重复的 getCachedSystemSettings() 调用:已修复,两个 rectifier 块现共用同一次 settings 读取。
  • CodeRabbit – @/ 别名导入:不采纳,本文件对同目录模块统一使用相对导入(与 ./billing-header-rectifier 等保持一致),详见行内回复。

验证:bun run build / typecheck / 改动文件 lint 干净 / 全量 6370 测试通过。

…nflict rectifier)

- 迁移序号冲突:将本 PR 的 system-message-rectifier 列迁移移至 0106
  (drizzle 重新生成为 0106_stiff_dormammu.sql),让出 0105 给 dev 已合入
  的 0105_chief_rocket_racer.sql(thinking-effort-conflict rectifier)。
- src/repository/system-config.ts 降级链堆叠:
  最新层先剥离 enableSystemMessageRectifier,次层再剥离
  enableThinkingEffortConflictRectifier,再回到原有 billHedgeLosers 等
  既有降级路径,确保两轮新增列对老库的兼容性。
- tests/unit/repository/system-config-update-missing-columns.test.ts:
  原 "仅缺 enable_thinking_effort_conflict_rectifier 列" 测试改为
  覆盖 enableSystemMessageRectifier(最外层),新增一项覆盖连续剥离两
  个新列直到 billHedgeLosers 层的回归断言。

@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: b7619abb59

ℹ️ 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 +2536 to +2539
if (systemMessageRectifierEnabled) {
const systemMessageResult = rectifySystemMessages(
session.request.message as Record<string, unknown>
);

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 Apply the rectifier before raw count_tokens passthrough

When Claude Code sends the same messages shape to /v1/messages/count_tokens, this new rectifier is skipped because this block lives under the standard preprocessing gate (bypassForwarderPreprocessing), while endpoint-policy.ts marks /v1/messages/count_tokens as raw passthrough and the later raw path forwards the original body bytes. In that context, strict Anthropic-compatible upstreams still receive messages[].role === "system" and can reject the token-count request before the actual /v1/messages call is made, so the fix does not cover the preflight path that uses the same invalid role.

Useful? React with 👍 / 👎.

@coderabbitai coderabbitai 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
src/repository/system-config.ts (2)

1109-1162: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

更新降级链只裁剪了 updates,但 returning(...) 仍在重新引入缺失列。

这里的注释已经点出了“不要重新引入较新的列”,但当前只对 updates 做到了,returningWithoutPassThrough / returningWithoutHighConcurrencyMode / returningWithoutCodexAndHighConcurrency 仍然来自原始字段集。这样在旧 schema 上,即使 set(...) 已经去掉了缺失列,returning(...) 也会因为重新选择这些列继续报 42703,导致本来应该成功的降级更新最终失败。

returning 需要和 updates 一样,从 returningWithoutNonConversationFallback 开始继续逐层裁剪。

可参考的修正方向
           try {
             // Continue pruning from the already-reduced object, otherwise the
             // freshly removed `fakeStreamingWhitelist` (and any other newer
             // columns) would be reintroduced and fail again on legacy schemas.
             const withoutPassThroughUpdates = { ...updatesWithoutNonConversationFallback };
             delete withoutPassThroughUpdates.passThroughUpstreamErrorMessage;
+            const {
+              passThroughUpstreamErrorMessage: _omitReturningPassThrough,
+              ...returningWithoutPassThroughAfterNonConversation
+            } = returningWithoutNonConversationFallback;
             [updated] = await executor
               .update(systemSettings)
               .set(withoutPassThroughUpdates)
               .where(eq(systemSettings.id, current.id))
-              .returning(returningWithoutPassThrough);
+              .returning(returningWithoutPassThroughAfterNonConversation);
           } catch (passThroughFallbackError) {
@@
             const downgradedUpdates = { ...updatesWithoutNonConversationFallback };
             delete downgradedUpdates.passThroughUpstreamErrorMessage;
             delete downgradedUpdates.enableHighConcurrencyMode;
             delete downgradedUpdates.publicStatusWindowHours;
             delete downgradedUpdates.publicStatusAggregationIntervalMinutes;
             delete downgradedUpdates.ipExtractionConfig;
             delete downgradedUpdates.ipGeoLookupEnabled;
+            const {
+              enableHighConcurrencyMode: _omitReturningHighConcurrency,
+              publicStatusWindowHours: _omitReturningPublicStatusWindowHours,
+              publicStatusAggregationIntervalMinutes:
+                _omitReturningPublicStatusAggregationIntervalMinutes,
+              ipExtractionConfig: _omitReturningIpExtractionConfig,
+              ipGeoLookupEnabled: _omitReturningIpGeoLookupEnabled,
+              ...returningWithoutHighConcurrencyAfterNonConversation
+            } = returningWithoutPassThroughAfterNonConversation;
@@
               [updated] = await executor
                 .update(systemSettings)
                 .set(downgradedUpdates)
                 .where(eq(systemSettings.id, current.id))
-                .returning(returningWithoutHighConcurrencyMode);
+                .returning(returningWithoutHighConcurrencyAfterNonConversation);
@@
+              const {
+                codexPriorityBillingSource: _omitReturningCodexPriorityBillingSource,
+                ...legacyReturning
+              } = returningWithoutHighConcurrencyAfterNonConversation;
               [updated] = await executor
                 .update(systemSettings)
                 .set(legacyUpdates)
                 .where(eq(systemSettings.id, current.id))
-                .returning(returningWithoutCodexAndHighConcurrency);
+                .returning(legacyReturning);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/repository/system-config.ts` around lines 1109 - 1162, The PR's downgrade
chain only prunes the update payloads but leaves the returning column lists
intact, causing returning(...) to reintroduce missing columns and trigger 42703;
fix by deriving each subsequent returning set from
returningWithoutNonConversationFallback (not the original full set) and prune
the same missing columns in lockstep with updates — update usages of
returningWithoutPassThrough, returningWithoutHighConcurrencyMode, and
returningWithoutCodexAndHighConcurrency to be created/used by cloning and
deleting the matching column names (e.g., remove
passThroughUpstreamErrorMessage, enableHighConcurrencyMode,
publicStatusWindowHours, publicStatusAggregationIntervalMinutes,
ipExtractionConfig, ipGeoLookupEnabled, codexPriorityBillingSource) so each
.returning(...) matches the corresponding pruned .set(...) call (refer to
variables returningWithoutNonConversationFallback, returningWithoutPassThrough,
returningWithoutHighConcurrencyMode, returningWithoutCodexAndHighConcurrency and
the update/executor.update(...).set(...) blocks).

476-517: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

继续降级读取时不要切回原始字段集。

这里从 selectionWithoutNonConversationFallback 回退到 selectionWithoutPassThrough / selectionWithoutHighConcurrencyMode / selectionWithoutCodexAndHighConcurrency,会把前面已经剥离掉的列重新加回来,尤其是 allowNonConversationEndpointProviderFallbackenableThinkingEffortConflictRectifier。在同时缺少多列的旧库上,这会把本应成功的兼容读取一路打回到更小字段集,已有配置被默认值覆盖。

建议后续几层 fallback 都基于 selectionWithoutNonConversationFallback 继续删字段,而不是回到预先定义的原始 selection。

可参考的修正方向
-          try {
-            const [row] = await db
-              .select(selectionWithoutPassThrough)
+          const {
+            passThroughUpstreamErrorMessage: _omitPassThrough,
+            ...selectionWithoutPassThroughAfterNonConversation
+          } = selectionWithoutNonConversationFallback;
+
+          try {
+            const [row] = await db
+              .select(selectionWithoutPassThroughAfterNonConversation)
               .from(systemSettings)
               .orderBy(asc(systemSettings.id))
               .limit(1);
             return row ?? null;
           } catch (passThroughFallbackError) {
             if (!isUndefinedColumnError(passThroughFallbackError)) {
               throw passThroughFallbackError;
             }
@@
-            const [row] = await db
-              .select(selectionWithoutHighConcurrencyMode)
+            const {
+              enableHighConcurrencyMode: _omitHighConcurrency,
+              ipExtractionConfig: _omitIpExtractionConfig,
+              ipGeoLookupEnabled: _omitIpGeoLookupEnabled,
+              ...selectionWithoutHighConcurrencyAfterNonConversation
+            } = selectionWithoutPassThroughAfterNonConversation;
+
+            const [row] = await db
+              .select(selectionWithoutHighConcurrencyAfterNonConversation)
               .from(systemSettings)
               .orderBy(asc(systemSettings.id))
               .limit(1);
             return row ?? null;
@@
-              const [row] = await db
-                .select(selectionWithoutCodexAndHighConcurrency)
+              const {
+                codexPriorityBillingSource: _omitCodexPriorityBillingSource,
+                ...selectionWithoutCodexAfterNonConversation
+              } = selectionWithoutHighConcurrencyAfterNonConversation;
+
+              const [row] = await db
+                .select(selectionWithoutCodexAfterNonConversation)
                 .from(systemSettings)
                 .orderBy(asc(systemSettings.id))
                 .limit(1);
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/repository/system-config.ts` around lines 476 - 517, The current fallback
chain reverts from selectionWithoutNonConversationFallback back to earlier
selection sets (selectionWithoutPassThrough,
selectionWithoutHighConcurrencyMode, selectionWithoutCodexAndHighConcurrency),
which can reintroduce columns like allowNonConversationEndpointProviderFallback
and enableThinkingEffortConflictRectifier and cause older DBs to fall back too
far; change the fallback logic so each subsequent catch continues from
selectionWithoutNonConversationFallback by creating narrower selections derived
from it (i.e., remove additional problematic columns from
selectionWithoutNonConversationFallback for the later tries) instead of
switching back to the original selection variables (selectionWithoutPassThrough,
selectionWithoutHighConcurrencyMode, selectionWithoutCodexAndHighConcurrency),
and update the try/catch blocks that query systemSettings to use those derived
selections.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@src/repository/system-config.ts`:
- Around line 1109-1162: The PR's downgrade chain only prunes the update
payloads but leaves the returning column lists intact, causing returning(...) to
reintroduce missing columns and trigger 42703; fix by deriving each subsequent
returning set from returningWithoutNonConversationFallback (not the original
full set) and prune the same missing columns in lockstep with updates — update
usages of returningWithoutPassThrough, returningWithoutHighConcurrencyMode, and
returningWithoutCodexAndHighConcurrency to be created/used by cloning and
deleting the matching column names (e.g., remove
passThroughUpstreamErrorMessage, enableHighConcurrencyMode,
publicStatusWindowHours, publicStatusAggregationIntervalMinutes,
ipExtractionConfig, ipGeoLookupEnabled, codexPriorityBillingSource) so each
.returning(...) matches the corresponding pruned .set(...) call (refer to
variables returningWithoutNonConversationFallback, returningWithoutPassThrough,
returningWithoutHighConcurrencyMode, returningWithoutCodexAndHighConcurrency and
the update/executor.update(...).set(...) blocks).
- Around line 476-517: The current fallback chain reverts from
selectionWithoutNonConversationFallback back to earlier selection sets
(selectionWithoutPassThrough, selectionWithoutHighConcurrencyMode,
selectionWithoutCodexAndHighConcurrency), which can reintroduce columns like
allowNonConversationEndpointProviderFallback and
enableThinkingEffortConflictRectifier and cause older DBs to fall back too far;
change the fallback logic so each subsequent catch continues from
selectionWithoutNonConversationFallback by creating narrower selections derived
from it (i.e., remove additional problematic columns from
selectionWithoutNonConversationFallback for the later tries) instead of
switching back to the original selection variables (selectionWithoutPassThrough,
selectionWithoutHighConcurrencyMode, selectionWithoutCodexAndHighConcurrency),
and update the try/catch blocks that query systemSettings to use those derived
selections.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 9d0a5675-0e13-4390-9c41-6630d26fb1f5

📥 Commits

Reviewing files that changed from the base of the PR and between 1d2bd1a and b7619ab.

📒 Files selected for processing (23)
  • drizzle/0106_stiff_dormammu.sql
  • drizzle/meta/0106_snapshot.json
  • drizzle/meta/_journal.json
  • messages/en/settings/config.json
  • messages/ja/settings/config.json
  • messages/ru/settings/config.json
  • messages/zh-CN/settings/config.json
  • messages/zh-TW/settings/config.json
  • src/actions/system-config.ts
  • src/app/[locale]/settings/config/_components/system-settings-form.tsx
  • src/app/[locale]/settings/config/page.tsx
  • src/app/v1/_lib/proxy/forwarder.ts
  • src/drizzle/schema.ts
  • src/lib/api-client/v1/openapi-types.gen.ts
  • src/lib/api/v1/schemas/system-config.ts
  • src/lib/config/system-settings-cache.ts
  • src/lib/utils/special-settings.ts
  • src/lib/validation/schemas.ts
  • src/repository/_shared/transformers.ts
  • src/repository/system-config.ts
  • src/types/special-settings.ts
  • src/types/system-config.ts
  • tests/unit/repository/system-config-update-missing-columns.test.ts
💤 Files with no reviewable changes (1)
  • drizzle/0106_stiff_dormammu.sql
✅ Files skipped from review due to trivial changes (8)
  • messages/en/settings/config.json
  • messages/ru/settings/config.json
  • messages/ja/settings/config.json
  • messages/zh-TW/settings/config.json
  • src/lib/api-client/v1/openapi-types.gen.ts
  • src/lib/config/system-settings-cache.ts
  • messages/zh-CN/settings/config.json
  • drizzle/meta/_journal.json
🚧 Files skipped from review as they are similar to previous changes (11)
  • src/app/[locale]/settings/config/page.tsx
  • src/lib/utils/special-settings.ts
  • src/types/system-config.ts
  • src/lib/api/v1/schemas/system-config.ts
  • src/lib/validation/schemas.ts
  • src/drizzle/schema.ts
  • src/app/[locale]/settings/config/_components/system-settings-form.tsx
  • src/repository/_shared/transformers.ts
  • src/types/special-settings.ts
  • src/actions/system-config.ts
  • src/app/v1/_lib/proxy/forwarder.ts

@ding113

ding113 commented Jun 11, 2026

Copy link
Copy Markdown
Owner

hi,对于涉及请求体变更的整流器,建议按照思考签名整流器的思路,在检测到特定报错模式之后被动整流。
主动对请求体进行修改可能引发意料外后果。

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:core area:i18n area:provider bug Something isn't working size/XL Extra Large PR (> 1000 lines)

Projects

Status: Backlog

Development

Successfully merging this pull request may close these issues.

2 participants