← 学习站 / ai-coding

来源:一次 Claude Code × 第三方网关的实测排查(2026-10-15)· 客户端版本 2.1.300
交叉验证:issue #77247 · issue #88345 · 结论绑版本,升级后须复验

Claude Code 走第三方网关时,1M 上下文被静默砍成 200K

网关明明支持 1M,长会话却在 200K 附近就显示 100% context used。根因在客户端:provider 被误判,未识别模型一律按 200K 兜底。解法是给实际发送的模型名加 [1m] 后缀——一行配置,窗口从 200K 到 1M。


TL;DR · 四条结论

  1. 这是客户端误判,不是网关限制 配置 ANTHROPIC_BASE_URL 指向第三方网关后,Claude Code 把 inference provider 误判为 firstParty,未识别模型一律按 200K 兜底——网关支持 1M 也用不上。
  2. 解法:把 [1m] 加在「实际发给网关的模型名」上 窗口 200K → 1M,客户端会带上 anthropic-beta: context-1m-2025-08-07,而发送前后缀被剥离,网关收到的仍是原模型名。
  3. 写在 behavesAs 里的 [1m] 不管用 它只影响客户端按哪个已知模型对待,不改变发出的模型名,也就进不了 1M 路径。
  4. 不需要 CLAUDE_CODE_MAX_CONTEXT_TOKENS 实测仅加 [1m] 即可从 200K 变 1M;该变量在此场景是冗余项。
时效性警告:本文结论建立在 2.1.300 的未文档化客户端行为上(模型名 [1m] 后缀解析 + 发送前剥离)。相关上游 issue 已被 stale 关闭、未修复,这块随时可能变。每次升级 Claude Code 后请重跑文末「验证」一节(一条命令,约 8 秒),不要默认仍然有效。

症状与判别

先分清两种「超限」,处理方向相反

长会话在 200K 附近就显示 100% context used,而模型的真实上限远高于此。实测中一次会话峰值 190,240 tokens 即满,59 条 API 请求的 betas 里一条 context-1m 都没有。

最快判别法:不跑 A/B,看 /context 两点——窗口值是不是 200k;超限后还能不能跑。

超限后照常出 token、不报错

显示 297.7k/200k (149%)、Context exceeds the 200k-token limit…,会话却照常工作。这是铁证:200K 是客户端误判,网关真实上限更高。

处理 → 加 [1m] 后缀(本文方案)

超限后立刻被上游拒

报 prompt is too long / 413 / 网关包装的同类错误——那才是网关真的卡在 200K。

处理 → 调低 CLAUDE_CODE_AUTO_COMPACT_WINDOW=200000 让压缩提前,不要加 [1m]

两种情形表象像、处理相反,先分清再动手。

根因 · 客户端侧,非网关

provider 误判 → 目录关掉 → 兜底 200K

provider 解析函数只看内部 gateway 认证状态,不看 ANTHROPIC_BASE_URL 的 host(以下摘自 2.1.300 二进制内嵌 JS,符号名已按原文保留):

// provider 解析:只认内部 gatewayAuth 状态,不看 base URL host
function En(){ if (By()) return "gateway";          // By() = 内部 gatewayAuth 状态
               return USE_BEDROCK ? "bedrock" : … : "firstParty"; }
function kd(){ let e = process.env.ANTHROPIC_BASE_URL;
               return !e || new URL(e).host === "api.anthropic.com"; }

只设 ANTHROPIC_BASE_URL 时:By() 为假 → provider 落 firstParty,而 kd() 又为 false → 原生 1M 分支一个都不命中 → 窗口兜底 200000。

声明窗口的函数从「当前生效的模型目录」取值,而目录开关同样卡在 provider:

function wkt(...){ if(ys(a.CLAUDE_CODE_MODEL_CATALOG)) return "env_off";
                   … if(!Aa()) return "not_first_party";  // Aa() 要求 firstParty && Si()
                   … }

not_first_party → 目录判为 off → 窗口声明函数一路返回 null/undefined → 回落到兜底常量 200000。注意:内置目录里 claude-sonnet-4-5 声明的那条 200K根本没被读到,真正生效的是兜底值。

1M 的唯一入口是对模型名本身打标:

function iNt(e){ return /\[1m\]/i.test(e) }          // [1m] 检测
function QVn(e,n){ return (iNt(e) || …) && !jr(e) }  // 命中即视作 1M

模型名不带标记 → 检测为假 → 200K。

解法

给模型名加 [1m],label 保持干净

在 ~/.claude/settings.json 里,给每个要跑 1M 的模型在 modelPicker 行里改 model 字段:

"model": "your-model[1m]",
"modelPicker": {
  "options": [
    { "model": "your-model[1m]", "label": "your-model",
      "behavesAs": "claude-sonnet-4-5" }
  ]
}

也可以临时用命令行验证:claude --model 'your-model[1m]'。

只给确实支持 1M 的模型加。模型物理上只有 128K/200K 却加了 [1m],会导致自动压缩不触发、最终被上游返回 Context Window Exceeded 而中断会话。

改完必须重启,并看 /context 的 Model 行确认

settings.json 是启动时读取的,改完要重开 Claude Code 才生效(当前会话不受影响)。重启后:

踩过的坑:改完配置没重启就去看 /context,看到仍是旧模型名 / 200k / 149%,差点误判成「方案无效」。实际只是配置尚未加载。

验证

A/B 对照:只差后缀,其余全同

同一网关、同一模型,只差后缀,端到端实测(客户端 2.1.300,2026-10-15):

组--model实际发出的 modelbetas 含 context-1mcontextWindow结果
Ayour-modelyour-model ✗200000OK
Byour-model[1m]your-model(后缀已剥离) ✓1000000OK

B 组请求成功返回,说明网关接受了带 1M beta 的请求——端到端闭环,不是只看客户端标记。

复现(约 8 秒,一条命令)

claude --model 'your-model[1m]' --print --output-format json 'Reply with exactly: OK' \
  | python3 -c "import sys,json; m=json.load(sys.stdin)['modelUsage']; \
                print({k:(v['contextWindow'],v['canonicalModel'],v['provider']) for k,v in m.items()})"
# 期望:('…[1m]': (1000000, 'claude-sonnet-4-5', 'firstParty'))

再确认请求头真的带了 beta(取上一步的 session_id):

grep -o '"context-1m-2025-08-07"' \
  ~/.claude/projects/<你的项目路径>/<session_id>.jsonl | head -1

注:输出里 provider 仍显示 firstParty——说明 [1m] 走的是「模型名打标」这条独立路径,不依赖 provider 被正确解析成 gateway。别拿 provider 字段判断方案是否生效,看 contextWindow。

备选方案

另外两条路,及其取舍

方案说明为何没选
CLAUDE_CODE_USE_GATEWAY=1
+ ANTHROPIC_AUTH_TOKEN
强制 provider 解析为 gateway;社区实测 200k → 967k,662K 请求网关收下 它走「原生 1M 模型」分支,只认 Sonnet 5+ / Opus 4.7+,旧一代模型不在列;且要求 base URL 为 https
CLAUDE_CODE_MAX_CONTEXT_TOKENS 官方文档:未识别的 provider / custom 拼写且不带 [1m] 时直接生效 实测不需要——只加 [1m] 就够了;在此场景是冗余变量
覆盖模型目录
(CLAUDE_CODE_MODEL_CATALOG_URL 等)
让目录开关返回 env_off 走自建目录 未验证加载路径,风险高

时效性检查清单

升级 Claude Code 后必做

排查方法论

这次走的弯路,值得记

本坑最初用了错方法:直接反编译二进制、自行推导窗口逻辑,连续三次给出错误结论(先说「1M 生效」→ 再说「配置正确不用改」→ 又自己推翻)。绕了两小时才想到搜社区,而 GitHub issue #77247 早已给出验证过的根因 + 一行解法。

  1. 先搜,再反编译 第三方产品(尤其 Claude Code 这类频繁改版的)行为异常,先搜官方文档对应章节 + GitHub issues(含已关闭的 stale issue,答案常在里面)+ 社区文章,再考虑反编译。反编译适合验证结论,不适合发现结论。
  2. 先分层再深入 同样报「100% context used」,可能是窗口层(本文:窗口被砍)也可能是门控层(压缩不触发)。层搞错了,越查越偏。
  3. 拿症状反推假设 「超限却还能跑」这种自相矛盾的现象,本身就指向「客户端的某个假设是错的」,别忽略它。