1. Terminal-Bench 2.1 多轮执行:Claude Code 先卡在后端,不是卡在任务
在 Terminal-Bench 2.1 里跑多轮执行,Claude Code 最常见的报错不是某条 shell 命令写错,而是API Error: 429 rate_limit_error或Connection error: upstream timeout。任务本身还没崩,模型请求先断了。先把后端切到 TaoToken:到 TaoToken 官网(https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=tb21_backend)获取 Key,再把 Base URLhttps://taotoken.net/api填进 Claude Code 的ANTHROPIC_BASE_URL。这一步做完,后面每一轮执行才有稳定的请求出口。
最近开源社区在讨论一类“持久执行 harness”,比如 LongHorizon-Harness,核心思路是把长任务拆成有界步骤:规划、执行、验证、检查点或恢复,每一步尽量用 fresh context,避免上下文污染。它不训练新模型,而是套在 Claude Code、Codex 这类 agent 外面,让 agent 能连续跑多轮。Terminal-Bench 2.1 就是这种模式的典型试验场:任务跨终端、文件系统、依赖安装、产物校验,跑几十步甚至上百步很常见。
问题也出在这里。多轮执行意味着每一轮都要向模型后端发请求。如果后端不稳定、限流、超时,或者配置只对交互式对话生效、对 harness 子进程不生效,就会出现“任务逻辑没坏,执行链先断”的情况。终端自动化工程师最怕的不是模型不会做,而是跑到第 37 步时 Claude Code 突然连不上端点,检查点无法继续,前面三十多步的验证状态变成孤岛。
所以这篇不讨论模型能力排名,只做一件事:把 Claude Code 逐步接到 TaoToken,让它在 Terminal-Bench 2.1 的多轮 harness 里稳定执行,并且把每一轮的 Token 消耗记下来。你可以按下面的步骤直接复现,也可以把它嵌进你自己的长任务 runner。
2. 先拿 TaoToken Key,再确认模型 slug 和套餐口径
在配置 Claude Code 之前,先把 Key 和模型名准备好。顺序不要反:先有可用 Key,再去改 agent 配置,最后跑 harness。否则你会在 401 和模型名错误之间来回排障。
第一步,打开 TaoToken 官网(https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=tb21_key_setup),注册或登录账号。第二步,进入控制台创建 API Key。建议直接走 API Keys 页面:
https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=tb21_keys
创建后复制 Key,不要把它写进 Git 仓库,也不要在 shell 历史里明文保留。推荐做法是导出到环境变量,或者放进本地密钥文件后再source:
# 把 YOUR_API_KEY 换成你在 TaoToken 控制台创建的 Key export TAOTOKEN_API_KEY="YOUR_API_KEY" # 确认变量已生效,注意不要把完整 Key 打印到公开日志 test -n "$TAOTOKEN_API_KEY" && echo "TAOTOKEN_API_KEY is set"第三步,确认你要用的模型 slug。Claude Code 需要ANTHROPIC_MODEL,Codex 需要 OpenAI 兼容的模型名,两者不要混。你可以到模型对话页面先做一次单轮验证:
https://taotoken.net/models/detail/chat?utm_source=taotoken_aicg_blog_end&utm_content=tb21_chat
在模型对话里发一句“只回复 pong”,确认 Key 可用、模型可调用。然后回到控制台看模型列表,把 Claude 系列对应的 slug 记下来。下面示例里我写claude-sonnet-4-5作为占位,实际请以 TaoToken 控制台展示的模型名为准。如果你的任务需要更长上下文或更强工具调用,可以在 Coding Plan 页面看套餐和额度口径:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=tb21_plan
这里要强调一点:不要把“拿到 Key”当成配置完成。Claude Code 有三层配置来源:shell 环境变量、~/.claude/settings.json、项目级配置。Terminal-Bench 2.1 的 harness 往往通过子进程启动 Claude Code,父进程环境变量是否继承、settings.json 是否被读取,都会影响最终请求走哪个 Base URL。下一节直接给出可复制配置。
3. Claude Code 接入 TaoToken:settings.json 与环境变量两种写法
Claude Code 接入自定义 Anthropic 兼容后端,关键是两个变量:ANTHROPIC_BASE_URL和ANTHROPIC_AUTH_TOKEN。Base URL 使用:
https://taotoken.net/api注意这个 Base URL 在工具配置里不要加 UTM 参数,保持干净。UTM 只用于官网和 deep link 的访问统计。
3.1 写法一:写入~/.claude/settings.json
如果你希望每次启动 Claude Code 都自动走 TaoToken,可以把配置写进用户级 settings:
{ "env": { "ANTHROPIC_BASE_URL": "https://taotoken.net/api", "ANTHROPIC_AUTH_TOKEN": "YOUR_API_KEY", "ANTHROPIC_MODEL": "claude-sonnet-4-5", "ANTHROPIC_SMALL_FAST_MODEL": "claude-haiku-4-5" } }存放路径通常是:
mkdir -p ~/.claude $EDITOR ~/.claude/settings.json改完后不要急着跑 harness,先用单轮命令验证:
claude -p "只输出 pong,不要调用任何工具" \ --output-format json \ --max-turns 1如果返回 JSON 里包含result和usage,说明 Claude Code 已经能从 TaoToken 拿到响应。如果报 401,优先检查ANTHROPIC_AUTH_TOKEN是否就是YOUR_API_KEY替换后的值;如果报模型不存在,回到模型对话页面确认 slug。
3.2 写法二:shell 环境变量,适合 harness 子进程继承
有些 terminal harness 不会读取~/.claude/settings.json,而是直接subprocess调用claude。这时最稳妥的是在父进程导出环境变量,让子进程继承:
export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_AUTH_TOKEN="${TAOTOKEN_API_KEY:?请先导出 TAOTOKEN_API_KEY}" export ANTHROPIC_MODEL="claude-sonnet-4-5" export ANTHROPIC_SMALL_FAST_MODEL="claude-haiku-4-5" # 检查关键变量 env | grep -E '^ANTHROPIC_(BASE_URL|MODEL|SMALL_FAST_MODEL)='ANTHROPIC_AUTH_TOKEN不要写成ANTHROPIC_API_KEY了事。Claude Code 对自定义端点的鉴权读取逻辑更偏向ANTHROPIC_AUTH_TOKEN。如果你同时设置了多个 Key 变量,可能被其他配置覆盖,排障时先用env | grep ANTHROPIC确认当前 shell 的最终值。
3.3 验证请求确实走了 TaoToken
Claude Code 有调试输出,可以用它确认请求目标:
claude --debug -p "输出当前可用的模型名" --max-turns 1在 debug 日志里搜索base_url或taotoken.net。如果看到的还是默认端点,说明 settings.json 没生效,或者当前 shell 的环境变量优先级更高。终端自动化里不要靠猜,靠日志。
4. Codex 用 config.toml,CC Switch 用三件套,别混协议
Claude Code 走 Anthropic 兼容协议,Codex 走 OpenAI 兼容协议。两者配置不能互相套。尤其不要把ANTHROPIC_*写到 Codex 的配置里,Codex 不认。
4.1 Codex:~/.codex/config.toml
Codex 的供应商配置写在 config.toml 里。下面是一个可用骨架,模型名和 provider 名按你的实际控制台信息替换:
model = "gpt-5-codex" model_provider = "taotoken" [model_providers.taotoken] name = "TaoToken" base_url = "https://taotoken.net/api" env_key = "TAOTOKEN_API_KEY" wire_api = "chat"然后在 shell 里导出 Codex 专用的 Key 变量:
export TAOTOKEN_API_KEY="YOUR_API_KEY"注意这里没有出现ANTHROPIC_AUTH_TOKEN。Codex 读取的是env_key指定的变量名,也就是TAOTOKEN_API_KEY。如果你把 Anthropic 的变量写进 Codex,最常见的结果是启动时报鉴权失败或直接走默认供应商。
4.2 CC Switch:三件套一次填对
如果你用 CC Switch 管理 Claude Code 的多供应商切换,核心就是三件套:
| 字段 | 值 |
|---|---|
| Base URL | https://taotoken.net/api |
| API Key | YOUR_API_KEY |
| Model | claude-sonnet-4-5(以控制台为准) |
在 CC Switch 里新建一个 provider,可以命名为TaoToken,把三件套填进去,然后切换到该 provider。切换后回到终端执行:
claude -p "只输出当前 provider 名称" --output-format json --max-turns 1如果 CC Switch 切换后仍报 401,检查它写入的是用户级 settings 还是项目级 settings,以及当前 shell 是否残留了旧的ANTHROPIC_BASE_URL。多供应商工具最怕的就是“UI 切了,环境变量没切”。
5. 把 TaoToken 配置注入 LongHorizon-Harness 的多轮循环
LongHorizon-Harness 这类项目的定位是外层执行循环,它自己不替代 Claude Code,而是让 Claude Code 按“规划、执行、验证、检查点或恢复”的节奏反复跑。对终端自动化工程师来说,最需要确认的是:harness 启动 agent 子进程时,TaoToken 的环境变量有没有带进去。
推荐用一个 wrapper 脚本统一注入,而不是依赖当前终端会话。这样无论你从 cron、systemd 还是 CI 里启动,配置都一致。
#!/usr/bin/env bash # 文件名:run_tb21_with_taotoken.sh set -euo pipefail # 1. 读取 Key,避免明文写在脚本里 : "${TAOTOKEN_API_KEY:?请先导出 TAOTOKEN_API_KEY}" # 2. 注入 Claude Code 需要的 Anthropic 兼容配置 export ANTHROPIC_BASE_URL="https://taotoken.net/api" export ANTHROPIC_AUTH_TOKEN="$TAOTOKEN_API_KEY" export ANTHROPIC_MODEL="claude-sonnet-4-5" export ANTHROPIC_SMALL_FAST_MODEL="claude-haiku-4-5" # 3. 单轮健康检查,确认后端可达 claude -p "只输出 pong,不要调用工具" \ --output-format json \ --max-turns 1 > /tmp/tb21_ping.json python3 - <<'PY' import json from pathlib import Path p = Path("/tmp/tb21_ping.json") data = json.loads(p.read_text()) print("backend check:", data.get("result", "")[:64]) print("usage:", data.get("usage")) PY # 4. 启动你的 harness # 下面这行只是占位,入口名请替换成 LongHorizon-Harness 的实际 CLI。 # 关键是:它必须继承当前 shell 的 ANTHROPIC_* 环境变量。 # python -m longhorizon_harness run \ # --task terminal-bench-2.1 \ # --agent claude \ # --step-limit 200 \ # --checkpoint-dir /tmp/tb21_checkpoints如果你的 harness 支持在配置文件里给 agent 传 env,也可以在那里写:
agent: name: claude env: ANTHROPIC_BASE_URL: "https://taotoken.net/api" ANTHROPIC_AUTH_TOKEN: "${TAOTOKEN_API_KEY}" ANTHROPIC_MODEL: "claude-sonnet-4-5"但要注意:YAML 里写${TAOTOKEN_API_KEY}是否会被展开,取决于 harness 是否做环境变量插值。如果没有插值,就还是回到 wrapper 脚本导出变量。多轮执行里,配置的确定性比配置的优雅更重要。
另外,harness 做检查点或恢复时,通常只恢复任务状态,不会重新读取 Key。所以 wrapper 必须在启动前就把 Key 准备好。如果 Key 在运行中轮换,最稳妥的做法是停止 harness,更新环境变量,再从上一个检查点恢复,而不是在子进程运行中热改配置。
6. 多轮 Token 消耗记录:每一步都落 JSONL
Terminal-Bench 2.1 的多轮执行可能跑几十小时。如果不记录 Token 消耗,你只知道“跑完了”,不知道钱花在哪一步、哪一次失败恢复最贵、缓存命中是否正常。下面给一个最小可用的 JSONL 记录方案。
先写log_usage.py,它从 stdin 读取 Claude Code 的 JSON 输出,把 usage 追加到日志:
#!/usr/bin/env python3 # 文件名:log_usage.py import json import os import sys import time from pathlib import Path LOG_PATH = Path(os.environ.get("TB21_USAGE_LOG", str(Path.home() / ".taotoken" / "tb21_usage.jsonl"))) def main(): raw = sys.stdin.read() if not raw.strip(): return data = json.loads(raw) # Claude Code 的 JSON 输出里 usage 可能在一级字段或 modelUsage 下 usage = data.get("usage") if not usage and isinstance(data.get("modelUsage"), dict): # 取第一个模型的使用量 usage = next(iter(data["modelUsage"].values()), {}) row = { "ts": int(time.time()), "session_id": data.get("session_id"), "input_tokens": usage.get("input_tokens", 0), "output_tokens": usage.get("output_tokens", 0), "cache_read_input_tokens": usage.get("cache_read_input_tokens", 0), "cache_creation_input_tokens": usage.get("cache_creation_input_tokens", 0), "total_cost_usd": data.get("total_cost_usd"), "duration_ms": data.get("duration_ms"), } LOG_PATH.parent.mkdir(parents=True, exist_ok=True) with LOG_PATH.open("a", encoding="utf-8") as f: f.write(json.dumps(row, ensure_ascii=False) + "\n") print(json.dumps(row, ensure_ascii=False)) if __name__ == "__main__": main()然后写report_usage.py,汇总日志:
#!/usr/bin/env python3 # 文件名:report_usage.py import json import os from collections import defaultdict from pathlib import Path LOG_PATH = Path(os.environ.get("TB21_USAGE_LOG", str(Path.home() / ".taotoken" / "tb21_usage.jsonl"))) def main(): if not LOG_PATH.exists(): print(f"log not found: {LOG_PATH}") return total = defaultdict(int) turns = 0 with LOG_PATH.open("r", encoding="utf-8") as f: for line in f: line = line.strip() if not line: continue row = json.loads(line) turns += 1 for key in ( "input_tokens", "output_tokens", "cache_read_input_tokens", "cache_creation_input_tokens", ): total[key] += int(row.get(key) or 0) print(f"turns: {turns}") print(f"input_tokens: {total['input_tokens']}") print(f"output_tokens: {total['output_tokens']}") print(f"cache_read_input_tokens: {total['cache_read_input_tokens']}") print(f"cache_creation_input_tokens: {total['cache_creation_input_tokens']}") if __name__ == "__main__": main()在 harness 每一步调用 Claude Code 后,把输出接给log_usage.py:
claude -p "Terminal-Bench 2.1 step-17:检查 /tmp/tb21/workdir 下的产物是否完整,只输出 JSON 结论" \ --output-format json \ --max-turns 1 \ | python3 log_usage.py跑完一段后查看汇总:
python3 report_usage.py下面是一张示例记录表,用来展示你应该关注哪些字段。实际数字以你的 JSONL 为准:
| 轮次 | 任务片段 | input_tokens | output_tokens | cache_read | 备注 |
|---|---|---|---|---|---|
| 1 | 环境探测 | 8,421 | 612 | 0 | 建立初始检查点 |
| 2 | 安装依赖 | 12,033 | 1,204 | 7,960 | 命中缓存,成本下降 |
| 3 | 执行编译 | 15,870 | 2,331 | 9,410 | 工具调用较多 |
| 4 | 验证产物失败 | 9,204 | 788 | 6,200 | 从检查点恢复 |
| 5 | 修复后重跑 | 13,555 | 1,602 | 8,870 | 只重做失败步骤 |
| 6 | 最终校验 | 7,912 | 503 | 5,100 | 任务完成 |
重点看三件事:第一,cache_read_input_tokens是否稳定出现,如果全是 0,说明缓存策略没生效或每步上下文差异太大;第二,失败恢复那一轮的 input 是否异常高,如果恢复步骤比正常步骤贵很多,检查检查点里是不是塞了过多历史状态;第三,total_cost_usd是否能在单步级别对账,不能对账就无法做预算控制。
7. Terminal-Bench 2.1 多轮排障清单
多轮执行出问题时,按下面顺序排查,不要一上来就怀疑模型。
7.1 429:限流或并发过高
如果日志里出现429 rate_limit_error,先降低并发。Terminal-Bench 2.1 的 harness 可能同时跑多个子任务,每个子任务又调 Claude Code,瞬间并发可能超过后端限制。处理方式:
# 示例:把并发从 8 降到 2,并加入退避重试 # 具体参数以你的 harness 为准 python -m your_harness run \ --task terminal-bench-2.1 \ --agent claude \ --concurrency 2 \ --retry-backoff 5同时在 Claude Code 侧限制单步最大轮次,避免一个步骤内部无限重试:
claude -p "执行当前步骤并给出验证结果" --max-turns 3 --output-format json7.2 401 / 403:Key 没生效或被覆盖
先确认当前 shell 最终变量:
env | grep -E 'ANTHROPIC|TAOTOKEN' | sed 's/=.*/=<redacted>/'再看~/.claude/settings.json是否有旧的 Key。如果你同时用了 CC Switch,检查它是否把配置写到了项目级.claude/settings.json。Claude Code 的配置优先级里,项目级可能覆盖用户级。排障时可以先临时清空当前 shell 的ANTHROPIC_AUTH_TOKEN,只保留 settings.json 测试。
7.3 超时:Base URL 不对或网络策略拦截
确认 Claude Code 请求的是https://taotoken.net/api,而不是默认端点。可以用 debug 模式验证:
claude --debug -p "ping" --max-turns 1 2>&1 | grep -i -E 'base_url|taotoken|http'如果超时发生在 harness 子进程里,但你在当前终端单跑claude正常,说明子进程没有继承环境变量。回到第 5 节,用 wrapper 脚本导出变量。
7.4 模型名错误:去模型对话页面确认
不同供应商对模型 slug 的命名可能不同。不要凭记忆写。到模型对话页面选一次模型,看请求详情里的模型名,然后填到ANTHROPIC_MODEL。Codex 同理,但 Codex 用的是 OpenAI 兼容模型名,不要填 Claude 的 slug。
7.5 消耗失控:给每一步设上限
多轮执行最怕某一步进入循环。除了--max-turns,还可以在 harness 层面设置单步 Token 预算。如果你用上面的 JSONL 记录,可以写一个简单的阈值检查:
# 查看最近 20 条记录的总 input tail -n 20 ~/.taotoken/tb21_usage.jsonl \ | python3 -c " import sys, json rows = [json.loads(l) for l in sys.stdin if l.strip()] total = sum(r.get('input_tokens', 0) + r.get('output_tokens', 0) for r in rows) print('last 20 turns tokens:', total) "如果某一步的 output_tokens 异常高,通常是模型在反复输出长解释或工具调用参数。回到任务定义,把验证条件写得更可判定,减少开放式指令。
8. 从模型对话到 Claude Code 文档:一条可跟做的 CTA 路径
如果你准备把 Terminal-Bench 2.1 的多轮执行跑起来,建议按下面顺序走一遍:
先到模型对话页面验证 Key 和模型是否可用:
https://taotoken.net/models/detail/chat?utm_source=taotoken_aicg_blog_end&utm_content=tb21_cta_chat根据任务时长和并发需求看 Coding Plan:
https://taotoken.net/coding-plan?utm_source=taotoken_aicg_blog_end&utm_content=tb21_cta_plan在控制台创建并管理 API Key:
https://taotoken.net/console/api-keys?utm_source=taotoken_aicg_blog_end&utm_content=tb21_cta_keys按 Claude Code 接入文档完成 settings.json 或环境变量配置:
https://taotoken.net/doc/ClaudeCodeAnthropic?utm_source=taotoken_aicg_blog_end&utm_content=tb21_cta_doc
最后再回官网确认最新入口和控制台能力:
https://taotoken.net/?utm_source=taotoken_aicg_blog_end&utm_content=tb21_cta_home
把 Base URLhttps://taotoken.net/api填进 Claude Code 后端后,你的 Terminal-Bench 2.1 多轮执行就不再依赖临时会话。每一步 fresh context,每一次失败从检查点恢复,每一轮 Token 消耗落到 JSONL。这样跑长任务,第二天看到的不是“它好像跑完了”,而是可复现的命令、可审计的日志、可对账的消耗记录。