你问“Codex控不了浏览器”,我第一反应是:先别急着怀疑Codex,也别急着怀疑浏览器,先怀疑中间那个传话的。Codex本身不是一个能直接点按钮的机器人,它是一个会调用工具的智能体。浏览器控制要打通一条很长的链路:Codex 通过 MCP 协议连接一个浏览器工具,那个工具再通过 Chrome DevTools Protocol 去指挥浏览器。任何一个环节掉链子,表象都一样:Codex好像没反应,或者报一个看不懂的错。这篇文章不是纸上谈兵的教程,是我实际排查这类问题时的完整思路,从API层、浏览器层到工具层,每一步怎么验证、怎么看日志、怎么定位根因,全部写出来,给同样被这个问题卡住的朋友一个可以直接照着做的排查路线。
1. 先搞清楚“控不了浏览器”是哪个环节断了
很多朋友一上来就问我:“Codex是不是不支持浏览器?”其实Codex对浏览器的支持完全取决于你有没有给它装“手”——也就是MCP里的浏览器工具。它自己是不长手的。
先把完整链路画在脑子里:你给Codex一句指令,Codex把指令交给大模型去理解,模型决定要调用哪个工具,比如“browser_navigate”或“browser_click”,然后Codex通过MCP协议把这次调用转发给浏览器工具,工具再通过CDP(Chrome DevTools Protocol)连接到一个真正在运行的浏览器实例,浏览器执行操作后把结果(截图、页面内容)返回给工具,工具再返回给Codex。这中间任何一段断了,你看到的都是“Codex控不了浏览器”。
所以排查的第一步不是去看浏览器,而是先看现象到底属于哪一种。
| 现象 | 大概率断掉的环节 |
|---|---|
| Codex回复了一堆话,但完全没有工具调用的痕迹 | 模型决策阶段,或者Codex没加载到浏览器MCP工具 |
| Codex明确说“调用browser_navigate”,但等半天没后续 | MCP连接或浏览器工具进程挂了 |
| Codex调用了工具,浏览器窗口没动,但最后返回了一个错误码 | 浏览器端,通常是CDP端口、扩展权限、页面兼容性问题 |
| 浏览器动了,但结果不对,比如截图全白或页面被拦截 | 浏览器执行阶段,常和登录态、反自动化检测有关 |
这里有一个极其重要的判断点:到底有没有产生“工具调用”?在Codex的终端界面里,它会展示当前调用的是哪个工具、参数是什么。如果连这个都没有,意味着Codex压根没拿到浏览器工具,问题根本不在浏览器,而在工具配置。如果调用了但浏览器不动,那才需要往下查MCP和浏览器本身。
我实测过很多次,有一半以上的人卡在“工具根本没被加载”,而不是浏览器不听话。下面每一个大环节我都会给具体的验证方法,你按顺序走一遍,基本能锁定根因。
2. 先查Codex本体:API请求通不通,模型认不认
2.1 让Codex输出最详细的日志
排查任何工具问题,第一件事是开日志。Codex的日志会直接告诉你它有没有成功发起API请求、API返回了什么、MCP工具加载了多少个。默认状态下的输出太干净了,什么都看不出来。
我用过的做法是:在命令行里运行Codex时加上详细日志参数。不同版本参数不太一样,有的是codex --verbose,有的是设置环境变量CODEX_LOG_LEVEL=debug,你直接跑一下codex --help看一眼它支持哪种。如果都不支持,就在启动命令前面加RUST_LOG=debug,很多Rust写的CLI工具认这个环境变量,Codex也认。
日志会打到标准输出,但也会写到本地目录,一般是在~/.codex/log/下,文件名带时间戳。出问题的时候去翻这个文件,搜索mcp、tool、error这类关键词,比看终端输出强十倍。
2.2 Endpoint返回的典型错误
Codex运行时的核心依赖是API请求。只要请求层出问题,后面什么都做不了。日志里如果出现类似“local proxy failed while handling codex endpoint /responses”这种消息,请立刻意识到:这是本地网络层的问题,不是Codex本身的问题。
/responses是Codex调用模型时走的API端点。错误里出现“local proxy failed”意味着Codex发出的请求根本没有到达服务端,而是被本机的某个代理/网关处理时中断了。常见原因包括:系统HTTP代理配置指向了一个失效的地址、抓包工具劫持了HTTPS请求、本地转发服务的端口被占用、代理规则把/responses这几条路径识别错了。
我当时排查这个错误的时候,最快的方法是把本地代理工具先停掉,让Codex直连跑一次。如果直连能通,那根因100%在代理配置,去修代理规则而不是去重装Codex。还有一个容易被坑的点:你明明在系统设置里关了代理,但终端环境变量里还留着HTTP_PROXY和HTTPS_PROXY,这种残留环境变量会持续生效,非常隐蔽。
2.3 token失效与认证问题
日志里如果出现auth token is unavailable这类报错,说明Codex没有拿到有效的API密钥。要分两种情况看:一是你还没登录,需要运行登录命令走一遍浏览器认证;二是你配置了多个API密钥来源,比如环境变量里的OPENAI_API_KEY和Codex自己存的登录token冲突了。
Codex的认证信息通常存在~/.codex/auth下面,如果之前登录过但后来失效了,直接注销再重新登录一下。这里有个细节:如果你同时在系统环境变量和Codex配置文件里都设置了密钥,Codex会优先读环境变量。有些朋友在config里改了密钥,但环境变量里还是旧值,导致怎么改都不生效。
2.4 模型参数不支持工具调用
热词里有一条很典型:the 'gpt-5.6-sol' model is not supported when using codex with a ...。这个报错我见过不少次。Codex对模型是有白名单校验的,不是所有模型都支持它的工具调用协议。如果你通过某种方式让Codex接入了第三方模型服务,而这个模型服务不支持OpenAI风格的tool call,那Codex就永远无法触发浏览器工具回调。
排查方法很简单:在Codex配置里暂时改回官方默认模型,或者换成它明确支持的模型,再跑一次。如果换回支持模型后浏览器工具就能用了,那说明第三方后端对工具调用的支持有问题,不是MCP配置的问题。很多人在这一步误判,不停去调MCP server,其实根子在模型后端。
3. 再查浏览器端:远程接口是否真的在线
3.1 浏览器没开启调试端口
如果Codex已经成功调用了浏览器工具,但工具去连浏览器的时候连不上,最常见的原因是浏览器没有开CDP调试端口。Chrome和Edge都支持通过--remote-debugging-port参数开启,如果你用的是浏览器扩展MCP,或者用CDP直连方式,这一步是必须的。
我一般会在本机手动启动一个独立的浏览器实例来测试:
"/Applications/Google Chrome.app/Contents/MacOS/Google Chrome" \ --remote-debugging-port=9222 \ --user-data-dir=/tmp/codex-browser-profileWindows下等价写法是带引号的chrome.exe路径,加同样的参数。开启后,直接在浏览器地址栏访问http://localhost:9222/json,如果能看到一堆JSON,说明端口开好了;如果拒绝连接,说明没开成功。
这里有个经验:如果系统里已经有一个Chrome在跑,你再用命令行带参数启动,往往不会生效,因为Chrome会把命令转发给现有进程。解决方法是一定要带一个独立的--user-data-dir,强制启一个全新的浏览器实例。
3.2 浏览器扩展MCP的连接状态不可全信
有些朋友用的是Chrome扩展的方式,扩展面板里明明显示“connected”,但Codex就是控制不了。为什么?因为扩展的“connected”只代表WebSocket连上了,不代表它把操作权限完整暴露给了Codex。
我遇到过扩展的权限范围只被授权了“读取标签页”和“当前页面URL”,却没有授权“执行JavaScript”或“点击/输入”的权限。结果Codex调用browser_click工具时返回permission denied,但日志里显示的却是成功连接过。
另外,扩展的MCP服务端地址和Codex配置文件里填的地址必须要完全一致,包括路径。很多人扩展里开的是http://127.0.0.1:8080/mcp,Codex配置里写的是http://localhost:8080/mcp,看起来差不多,但有些严格校验的工具会因为host头不一致直接拒绝握手。建议直接复制扩展面板里显示的地址,不要手敲。
3.3 无头浏览器与页面兼容性
Codex控制浏览器经常用无头模式。无头模式不是不能点,而是它对一些页面来说特别容易被识别和拦截。你让Codex打开一个登录后才能操作的页面,或者打开一个带反自动化检测的页面,浏览器工具往往返回截图空白、点击无效、页面停留时间过长后超时。表象是“控制不了”,本质是页面不让你控制。
排查时临时切到有头模式试一次。把无头参数去掉,让浏览器窗口弹出来,看它到底执行到哪一步。很多情况下窗口弹出来你就能看到:要么是登录弹窗挡住了点击,要么是页面持续转圈,要么是验证码把整个页面盖住了。这时候的问题已经从“技术链路”变成了“页面适配”,解决方案是给Codex更明确的上下文,比如告诉它先点击登录按钮,也要给够操作等待时间。
4. 然后查MCP连接与工具权限:中间人最容易出问题
4.1 MCP server配置里的三个高频错误
Codex通过MCP连浏览器工具,配置文件一般在~/.codex/config.toml。我见过的高频错误就三种。
第一是启动命令写错。比如用Playwright时会写:
[mcp_servers.browser] command = "npx" args = ["-y", "@playwright/mcp@latest"]看着没错,但如果你本机node版本太低或者npm镜像源配置有问题,npx启动会失败,Codex启动时会标注这个server是“failed”状态。你需要在Codex启动日志里看MCP server的加载情况,是connected、failed还是压根没读到配置。
第二是端口冲突。有些浏览器工具会固定监听某个端口,如果你的另一个本地服务占了这个端口,MCP server进程能启动但监听失败,日志里会出现EADDRINUSE或ECONNREFUSED。
第三是配置了多个MCP server,其中一个挂了导致Codex整体发起工具调用的流程变慢。Codex不会因为一个工具挂了就整体不可用,但它的工具列表会缺项。你可以主动在Codex里问它“你现在有哪些工具”,如果它列出的工具里没有浏览器相关工具,那八成是MCP server挂了。
4.2 本地代理干扰:local proxy failed的真实含义
热词里那条“cc switch local proxy failed while handling codex endpoint /responses”值得单独说一下。它其实是一个本地代理切换工具在处理Codex请求时抛出的异常。很多人把这种报错当成OpenAI的问题来查,结果把密钥换了好几遍都不对。
这类工具的工作原理是拦截本机特定进程的HTTP请求,再把请求转发到目标服务。Codex的/responses请求格式和传统的/chat/completions不完全一样,有的代理工具没有跟上这个变化,规则里只处理了老接口,导致Codex新的/responses请求在本地就被拦下来,返回一个failed状态。
验证方法很简单:临时绕开代理工具,让Codex直连服务端。如果直连后工具调用和浏览器操作都正常,那问题就在代理工具的规则配置上,去更新代理工具、调整它的接口转发规则,或者把Codex加入直连名单。不要一边开着代理一边狂重装Codex,那是瞎耽误工夫。
我说的“本地代理”包括但不限于系统HTTP代理、抓包工具、各类本地网关程序。这里提醒一句:如果你在公司网络里,还要检查一下有没有全局的企业代理策略,这类代理有时会把非标准端口的请求全部拒绝。
4.3 工具权限和审批流程:卡住不等于死机
Codex的终端界面在调用敏感工具时,是会等用户审批的。我第一次用的时候也遇到过“怎么点了没反应”的错觉——其实是在终端最底下一行出现了Allow? [y/n]之类的提示,只是被输出刷上去了。
浏览器控制这类工具经常被Codex判定为高权限操作,尤其是点击、输入、读取浏览器全量历史这类动作。如果你是用API模式跑Codex,没有交互终端,工具调用权限默认可能是拒绝的,导致每次操作都失败。解决方法是给这个MCP server配置更宽松的权限,或者在交互式终端里先允许一次,让Codex记住。
还有一个权限坑是工具作用域。有些浏览器MCP工具允许你限制只操作特定域名。比如只允许example.com,那你让Codex打开google.com就会被拒。这个时候日志里会返回not allowed,但Codex可能只回你一句“打开失败”。我们习惯性以为是不支持,其实是不在允许列表里。
5. 我怎么一步步定位这个问题的:一套可复用的排查清单
5.1 先造一个最小复现场景
排查这类问题,最忌讳直接用你那个复杂的真实任务去试。任务越复杂,变量越多,你越分不清是哪个环节坏了。我每次都会先造一个最小场景:
“打开 https://example.com ,把页面标题告诉我。”
这个任务只需要一次导航、一次读取页面内容,不涉及登录、不涉及点击、不涉及复杂JS。如果这个都跑不通,那问题就很纯粹,直接按上面的链路查。如果这个能跑通,再把任务复杂度逐步往上加,比如“在百度搜索框输入测试关键词并点击搜索”,这样就能把问题精确到具体能力上。
5.2 旁路测试:绕开Codex直接调浏览器
二分定位法是排查链路问题的利器。我的做法是:先完全绕开Codex,直接用Playwright或者CDP脚本控制同一个浏览器环境,看能不能成功。
比如你用的是Playwright MCP,就直接写一个三行的Node脚本:
const { chromium } = require('playwright'); (async () => { const browser = await chromium.launch({ headless: false }); const page = await browser.newPage(); await page.goto('https://example.com'); console.log(await page.title()); await browser.close(); })();如果这个脚本能正常打开浏览器并打印标题,说明浏览器本身、驱动、环境依赖都没问题。如果这个脚本也报错,那问题在浏览器工具或驱动层,跟Codex半毛钱关系都没有。如果脚本能跑通但Codex不行,那问题一定在Codex到MCP之间,比如配置、权限、审批、协议版本。
这一步非常快,一分钟就能定位到“上游”还是“下游”,省下大量无头苍蝇时间。
5.3 日志关键词速查表
下面这组关键词我排查时几乎天天用。遇到报错先搜日志,搜到哪类关键词就去哪类环节处理,比反复试快很多。
| 日志关键词 | 真实含义 | 排查方向 |
|---|---|---|
local proxy failed | 本地代理/网关转发失败 | 检查代理工具、HTTP_PROXY、抓包工具 |
auth token is unavailable | 认证信息缺失 | 重新登录,检查环境变量 |
ECONNREFUSED | 目标端口拒绝连接 | 浏览器调试端口没开,或MCP server没起来 |
ENOTFOUND | 域名无法解析 | DNS、网络连通性 |
model is not supported | 模型不被Codex支持 | 换回默认模型 |
permission denied | 工具调用被拒绝 | 检查Codex审批流程和MCP权限设置 |
not allowed | 域名/操作不在授权范围 | 检查工具作用域配置 |
WebSocket disconnected | MCP连接中断 | 检查扩展/服务端稳定性,端口冲突 |
5.4 一些容易让人上火的边界情况
有几个坑不常遇到,一旦遇到会让人非常怀疑人生,这里提前给你打个预防针。
第一个是环境变量串味。你的终端里可能同时设置了OPENAI_BASE_URL、OPENAI_API_KEY、CODEX_API_KEY。如果OPENAI_BASE_URL指向一个不兼容的网关,Codex就会像“控不了浏览器”一样疯狂报错。建议排查前先env | grep -i openai看一遍,把所有相关环境变量列出来,看看有没有残留。
第二个是多个浏览器实例冲突。你的默认Chrome已经在跑,又开了一个调试端口的Chrome,结果MCP连到的那个实例是旧的、没有新页面、没有登录态。看起来像控制失败,其实是控制了另一个浏览器。把无关的浏览器进程全关掉,只留一个带调试端口的实例再试。
第三个是MCP server日志太多导致Codex输出被淹没。浏览器工具会返回大量页面截图和DOM内容,Codex上下文塞满之后会表现得特别迟钝。这种情况下就算链路没问题,也会给你一种“控不了”的错觉。可以限制MCP返回结果的大小,或者让Codex一次只做一件事。
第四个是权限存储的过期问题。Codex的token有时效性,你用桌面版和CLI版共用一套配置时,其中一个刷新token会把另一个踢下线。症状是浏览器工具偶尔能通、偶尔不通,毫无规律。遇到这种抽风式失败,优先重新登录Codex。
最后说点实际的排查体会
每次看到有人说“Codex控不了浏览器,求排查思路”,我就想先问一句:你的Codex到底有没有真正发起过浏览器工具调用?因为绝大多数人被卡住,不是浏览器不听话,而是工具链根本没打通。
我的经验是,先花五分钟开日志,再用最小场景跑一遍,再用旁路脚本测试一下浏览器工具本身。按这个顺序走,至少能排除80%的“假故障”。剩下那20%才是真正需要深入研究的模型兼容、页面反自动化、复杂权限问题。
还有一个小技巧:在Codex的配置里给MCP server分组命名的时候,用browser_chrome、browser_firefox这种明确的前缀,不要叫test或者server1。这样当Codex自己列工具列表时,你和它都能一眼看出哪个是浏览器的工具,排查起来会顺畅很多。