61 道题跑完那天晚上我盯着结果看了很久42 道通过18 道失败还有 1 道因为测试仓库本身就坏了判了无效没计入统计。如果只看通过率它比我预想的低了一截。但真正让我在意的不是那 42 道而是后面这 18 道。把它们摊开看表面错误五花八门——有超时的、有产物差一半的、有直接把不该动的文件给改了的。可我一道一道手动复跑、逐个归因之后发现这 18 道其实全死在同一类事情上。这个结论挺反直觉。官方演示里这种跑在终端里的编码 Agent 看起来无所不能你说需求它自己敲命令、读输出、改文件比我手动复制粘贴强多了。但实际一跑问题往往不在模型能力而在一个很具体、很底层的执行环节。如果你也在用终端里的编码 Agent或者正准备给这类工具搭一套自己的评测这篇文章应该能帮你省下不少冤枉时间。我会把 61 道题的来龙去脉、18 道失败如何收敛到同一个根因、以及我后来怎么绕开这个坑完整讲清楚。1. 这 61 道题不是算法题是从真实开发流里拆出来的“干活题”1.1 为什么不用官方 benchmark非要自己攒市面上其实有不少编码 Agent 的评测集模型厂商也爱拿高分说事。但我用下来的感受是官方题集太“干净”了。题干描述接近一份理想 PR 描述输入输出明确依赖已经装好仓库能独立构建跑起来也不需要跟终端里的交互式程序打交道。可真实开发不是这样的。真实场景里我经常会碰到这种时刻让 Agent 装个依赖它执行到一半终端弹出一个[Y/n]的确认让它合并 commit它打开一个git rebase -i的编辑器界面让它调试一段卡住的代码它一头扎进pdb交互调试器。这些在官方 benchmark 里几乎不存在但在我的日常工作里到处都是。所以我决定不用现成的题集而是从自己最近三个迭代的真实需求里把能转成可判题形式的任务全部捞出来不做筛选原样保留。1.2 题目怎么分类通过标准怎么定最终凑了 61 道。这个数字不是刻意凑出来的是我从真实工作流里捞完就是这么多个。类型分布大概是这样的任务类型题数通过通过率单文件修改 / 函数实现141392.9%多文件重构 / 逻辑迁移10770%Git 工作流分支 / 合并 / 回滚8562.5%Shell 脚本编写与调试12650%命令行工具调用与结果解析9444.4%现有代码排错与测试修复8787.5%合计614268.9%等等看到 42 18 60 你可能会问还有 1 道去哪了就是我开头说的那题测试仓库在准备阶段就坏了依赖树不完整跟 Agent 的行为无关我直接标记为“无效”所以最终有效题数是 60通过率按 70% 算。通过标准我卡得比较严。每道题都是在全新临时目录里跑的准备脚本把仓库 reset 到初始 commit再调用 Agent 的 CLI让它自己理解任务、自己操作。判定分三档自动测试全部通过、关键文件 diff 跟参考实现一致、随机抽 20% 人工复查。另外还加了几条否定项不能改坏无关文件、不能留临时文件、不能偷偷改 git remote。1.3 跑题环境必须锁死否则结果没法比我跑题是在同一台开发机上Agent 版本和模型版本都锁在同一个 commit。这一点极其重要。终端编码 Agent 的更新频率很高今天我测的是它明天可能就换了内核版本不锁你根本不知道 42 和 18 到底是谁跑出来的。每道题我给的超时上限是 15 分钟命令级超时是 5 分钟。也就是说某条命令如果挂住 5 分钟没输出评测脚本会直接把它杀掉判这道题失败。这个参数后来成了定位根因的关键线索。我本来以为 15 分钟足够宽裕结果超时恰恰是失败任务里最集中的表象。2. 通过的 42 道暴露了这类 Agent 真正可靠的工作区间2.1 从类型分布看它会什么、不会什么拿到分布表的时候有两个数字让我意外。第一个是“单文件修改 / 函数实现”接近满分14 道里只挂 1 道。这个我理解任务边界越明确Agent 越稳。第二个是“现有代码排错与测试修复”居然有 87.5% 的通过率比我想象的高不少。回头看排错题有个共性错误信息是直接的、可读的仓库又小Agent 非常擅长“读报错 → 定位代码 → 改掉 → 再跑测试”这个循环。通过率最低的是“命令行工具调用与结果解析”只有 44.4%。这一类题恰恰是日常终端使用里最高频的场景装依赖、查日志、启动服务、解析命令输出。结果它反而最拉胯。当时我还没意识到这个分类的低分其实已经暗示了后面那个根因。2.2 通过的题长什么样局部收束型任务举一个印象比较深的通过案例。有一道多文件重构题把utils.py里的一段日期解析逻辑拆成独立的date_utils.py模块然后更新所有调用方保证整个测试套件通过。难点在于有几个调用点藏在 f-string 里不是简单的utils.parse_date(...)这种一眼能看到的模式。Agent 怎么做的它先用grep -rn把所有引用点找出来列了一个清单然后逐个改文件。改完之后跑了一遍pytest发现有一个测试因为时区问题挂了它又回去把模块里的默认时区处理修掉再跑全绿。整个过程没有问我一句话。这道题能过是因为它符合一个模式任务被严格限定在某几个文件里并且有测试作为客观反馈环。每次改动后Agent 都能从pytest输出里拿到“对还是错”的信号于是它可以自我修正。我把这类任务叫“局部收束型任务”这是终端编码 Agent 最舒服的工作区间。2.3 通过不等于完美别把测试通过当成代码写得好说实话这 42 道“通过”里有相当一部分只是“能过测试”。人工复查时我发现13 道通过的单文件修改里有 4 道命名风格跟项目不一致有 2 道留下了调试用的print还有几道代码写得像“为了过测试而过的”逻辑绕得厉害。这也给了我一个重要提示如果拿这套题去评测 Agent 的能力光看通过率会高估它。能跑通测试跟代码写得像人写的、可维护是两码事。所以后来我把评测拆成两层第一层是功能对错自动判第二层是代码质量人工抽审。前者决定“能不能用”后者决定“是否值得合并”。3. 18 道失败的复盘从“错误各不同”到“全栽在同一类命令上”3.1 初看失败面板谁都会觉得是模型不行18 道失败任务放一起表面原因分布是11 个超时、5 个产物不符合预期、2 个命令直接报错退出。如果你只看这张表结论很自然这 Agent 能力不行呗。但我总觉得不对劲。超时占比超过一半这在自然语言编程里不正常——如果模型真的不理解任务它通常会很快交出一个错误答案而不是一直挂在那不动。超时意味着它可能“卡住”了而卡住往往不是能力问题是环境问题。我决定把 18 道题全部手动重跑一遍记录 Agent 每次执行的完整命令流。3.2 逐题复跑所有卡点都指向同一个画面复跑的时候我把 Agent 每一条命令的开始时间、结束时间、退出码、输出尾部都记录下来。然后我发现了一个非常明显的规律这 18 道题里Agent 都在某一时刻运行了一条“会进入交互模式”的命令然后就没有然后了。举几个例子。题 44让 Agent 在当前仓库里把最近 3 个 commit 合并成 1 个。它执行了git rebase -i HEAD~3。这一步需要打开一个文本编辑器让人改 pick / squash 计划并保存退出。Agent 没有手也没有眼睛能看编辑器于是它卡住了直到命令级超时被杀。但我扒开它的历史看其实它在执行 rebase 之前就已经分析好了要把哪个 commit 标记为squash。它只是打不开那个编辑器。题 52让它用 Python 计算一组数据并导出 CSV。它启动了python3进入了 REPL输入了一行计算代码。但 REPL 的输出被管道缓冲了Agent 等了好一会儿也没等到结果超时。那个数据量很小计算本身连一秒都用不了。题 58修一个测试挂起的问题。Agent 用pytest --pdb进入调试器卡住。还有一道 Shell 脚本题它在验证脚本时直接跑了个bash -i等待输入把测试流程挂死。你发现没有这些任务前面的 90% 它都做对了模型不是不懂题差的就是最后那一步——程序进入了交互模式而执行环境里没有“人”在终端前面按回车、输命令、保存文件。3.3 决定性实验给命令执行器加一层“假屏幕”18 道全过为了验证这个猜想我做了一个很直接的决定把所有命令的执行方式从“非交互子进程”改成“在伪终端PTY里运行”。用一句命令就能模拟比如script -qec 命令 /dev/null或者在评测脚本里用tmux send-keys把命令发给一个后台会话。这相当于给 Agent 的命令执行器装了一块“假屏幕”让它执行的每个程序都以为自己跑在一个真实终端里。改完之后我重跑了这 18 道题。结果让我有点不太敢信18 道全部通过。有的题它甚至绕了远路比如那个 rebase 合并 commit 的题它这次没有直接git rebase -i而是预先设置了GIT_EDITORtrue让 rebase 过程跳过编辑器直接执行。这说明模型本身一点都不笨之前纯粹是被 no-TTY 环境坑了。这个实验说明了两件事第一18 道失败不是 18 个独立错误而是同一个执行层缺陷的 18 个变体第二如果想提升终端编码 Agent 在真实场景下的通过率改模型不如先改执行环境。3.4 为什么说“全是同一个原因”而不是“18 个问题”前两天我在写复盘笔记的时候试着把这 18 道题归因到具体模块。结果发现无论是“超时”还是“产物不符合预期”往底层追最终都落在了同一件事上Agent 的命令执行器默认跑在一个没有 TTY、stdin 关闭、stdout 被管道捕获的环境里。这个环境对人来说是“后台任务”对很多程序来说却是“非人类待遇”。就像同一块短路的路由器会让游戏掉线、网页打不开、视频加载失败。表面看是三个问题其实是同一个。终端编码 Agent 也是这样一条git rebase -i卡住一条pythonREPL 卡住一条pytest --pdb卡住看起来风马牛不相及实际上都是同一个执行环境缺陷在碰见不同交互式命令时的不同表现。4. 根因不是模型笨是执行器没有“屏幕”PTY 和交互式命令的恩怨4.1 终端编码 Agent 默认是怎么执行命令的要理解这个坑得先知道终端编码 Agent 的内部执行链路大概是怎样的。模型在输出里生成一段 bash 命令后Agent 的 runner 会拉起一个子进程去执行。默认实现通常是用subprocess起一个非交互的 shellstdin 直接连到/dev/nullstdout 和 stderr 通过管道回传给模型。回传的内容还会按末尾 N 行截断免得上下文爆炸。这个设计的好处是可控、安全、好解析。但代价很大所有被 Agent 启动的程序都生活在一个“没有屏幕、没有键盘”的世界里。它们能看到环境变量但看不到自己是被人操作还是被脚本调用。而不少程序恰恰会在这种情况下改变自己的行为。4.2 程序发现自己不在终端里通常有三种反应我总结了一下交互式程序在 no-TTY 环境下大体有三种反应。第一种是直接拒绝。比如vim、htop这类铁了心要占住整个界面的程序会直接报TERM environment variable not set或者stdin isnt a tty然后退出。这类其实还好Agent 看到报错能换策略。第二种是静默改变行为。比如ls不再输出颜色、git log不再进入分页器、Python 的 print 输出变成块缓冲而不是行缓冲。这种最阴因为命令还是能跑但输出时机和内容跟人在终端里看到的不一样Agent 可能因此误判。第三种是最致命的它在等待一个永远不会出现的输入。像read、input()、git rebase -i打开编辑器后的保存操作、npm install的确认提示它们会一直等下去。而 Agent 的 runner 如果设置了超时就会因为超时被强行终止如果没设超时整个任务就直接卡死。4.3 一个一眼看穿的小实验你可以自己在终端里跑一下这条命令python3 -c input(press enter: ) /dev/null在有 TTY 的终端里执行它会正常提示你按回车但加了 /dev/null之后它会立刻抛出一个EOFError: EOF when reading a line程序退出。bash也一样bash -c read -p continue? ans /dev/nullread 会立刻读到 EOF然后退出。终端编码 Agent 的默认执行环境约等于这种“读一个不存在键盘的输入”的状态。它不是没有能力处理交互而是它根本不在现场连“按键”这个动作都不存在。4.4 为什么官方演示永远看不出这个坑原因很简单官方 benchmark 里选用的命令大多是非交互式的。pytest、npm test、git diff、grep这些命令在 no-TTY 环境下也能正常输出结果所以模型可以顺利跑通。但一旦题目里包含一个需要用户确认、需要编辑器、需要 REPL 的步骤整个任务就可能翻车。真实终端使用里交互式程序躲都躲不开。安装依赖时的确认、修改 git 历史、连数据库客户端、进 Python REPL、跑调试器、启动一个 TUI 菜单工具哪一个不是交互式的只要任务流程中踩中一次Agent 就大概率整题报废前面 90% 的正确操作全部白费。4.5 这也解释了评测里最反直觉的那个现象我最初以为 18 道失败会是“均匀散布”在各类型里结果分布很不均匀Shell 脚本类和命令行工具调用类占了大多数而单文件修改类几乎全过。这不是模型能力的巧合分布而是执行器缺陷的必然结果。只碰普通命令的任务怎么都好说只要碰一次交互式命令就生死难料。这也给我提了个醒判断一个终端编码 Agent 好不好用不能只看它写代码有多厉害还要看它的“手”能不能在终端里正常工作。代码能力是模型决定的命令执行环境则是工程实现决定的。前者决定上限后者决定下限而真实落地场景里下限往往更重要。5. 绕开这个坑的四个实测方案以及一套可复用的评测思路5.1 方案一给命令执行器包一层伪终端这是治本的办法。核心思路就是别让 Agent 跑到裸 no-TTY 子进程里而是让它在伪终端里执行命令。最简单的验证方式script -qec 要执行的命令 /dev/nullscript会分配一个 PTY然后在里面运行目标命令。命令会误以为自己在一个真实终端里颜色、分页、交互行为都会恢复正常。对评测脚本来说一条命令就能改造。更工程化的做法是用tmux send-keys把命令发给一个后台会话或者写个 Python wrapper 用pty.openpty()自己管理伪终端。需要注意一个副作用PTY 模式下命令输出里会混入 ANSI 颜色转义和控制序列。这些人类看着舒服但大模型解析起来会有点吃力。我的做法是在回传给模型之前用正则把 ANSI 转义序列剥掉保留纯文本。这样既享受了 PTY 的“真实感”又不会弄脏模型的上下文。5.2 方案二在规则层禁止裸交互命令不是所有人都有权限改 Agent 的底层执行器。这时候可以退而求其次在系统提示词或者项目规则文件里把交互式命令直接禁掉并给出替代写法。我自己在用的规则会有这么几条禁止直接运行vim、htop、python裸 REPL、node裸 REPL、pdb这类交互式程序。必须用非交互模式替代python -c、node -e、mysql -e、psql -c。遇到需要确认的命令用管道预填输入比如printf y\n | command或者查一下命令有没有-y、--yes、--non-interactive这类参数。涉及 git 编辑器预先设置GIT_EDITORtrue或core.editor指向一个非交互脚本。所有命令统一加timeout 120前缀宁可快速失败不要无限等待。这套规则的效果立竿见影。改完规则后再跑那 18 道题就算不换 PTY 执行器也有很大一部分能过——Agent 会在执行前主动绕开交互命令。5.3 方案三把超时和输出截断调成“对 Agent 友好”的参数如果你的 Agent 还是会在某个交互命令上卡住至少让它快点失败。把命令级超时从 5 分钟缩短到 60 秒Agent 很快就能收到“这条命令超时了”的反馈然后它会换一个思路。很多卡死其实不是模型不想换而是它根本不知道卡了。输出问题也很关键。默认只回传最后 200 行的话长日志的前半段全都看不到。我现在的做法是让 Agent 把命令输出重定向到文件再通过tail -n 500 文件返回尾部内容。这样既保留完整日志又不会把上下文撑爆。实测下来这个改动对“命令输出很长导致 Agent 误判”的场景帮助很大。5.4 方案四把高频任务模板化让 Agent 没有机会踩坑这个方案看起来最笨但长期收益最高。我把日常会让 Agent 干的几类高频任务做成了模板查数据库一律用psql -c、mysql -e不启动交互式客户端。跑 Python一律先写临时脚本再python3 脚本.py不进 REPL。改 git 历史用git reset或者设GIT_EDITORtrue不走git rebase -i。调试不用pdb而是用python3 -m trace --trace或者直接在代码里临时加日志。这条思路的本质是把“人怎么习惯性绕开交互陷阱”的经验固化给 Agent。它不解决所有问题但能极大减少翻车概率。5.5 想自己搭一套回归评测我的建议和最小框架经历了这次测试之后我把这套 61 道题留了下来作为日常回归集。每次 Agent 升级或者我调整配置之后都会跑一遍。如果你想自己搭一套类似的评测我给你几个建议环境必须可复现最好容器化把 agent 版本、模型版本、系统依赖全部固定。每个任务一个独立目录用 git 快照作为初始状态任务之间互相不干扰。评价分两层自动判功能对错人工抽审代码质量。不仅记录最终答案还要记录每条命令的完整执行历史和时间戳。这次能定位到根因靠的就是完整命令流而不是只看结果。一个最小的评测循环大概是这个模样for task in tasks/*/; do cp -r $task/repo $WORKDIR/$(basename $task) cd $WORKDIR/$(basename $task) agent_cli run --task $task/prompt.md --timeout 900 judge --test $task/tests --repo $(pwd) done我用的是 Bash你完全可以用 Python、Go 或者其他任何语言重写核心就只有三件事准备隔离目录、调用 Agent、自动判定。有了这套东西以后任何关于“这 Agent 行不行”的争论都能用数据说话不用凭感觉。最后再分享一个小体会。我以前评测编码 Agent总爱盯着模型智商看——它能写多复杂的算法、能解多难的题。这次跑完 61 道题之后我的标准变了先看它在真实终端里会不会把自己卡死再看它能力上限在哪。那 18 道失败的题靠模型更新解决得很慢但靠执行环境和规则调整我当天就让它全部通过。工具链的问题就要用工具链的手段去解决。