如何启动多个 Codex 浏览器会话
为了同时跑多个 Codex session,我写了一个浏览器启动器。真正动手前,我先追了三个问题:Chrome DevTools MCP 是怎么注入 Codex 的,运行中能不能改;Chrome 的 profile 在哪里指定;CDP 端口又是怎么完成 auto connect 的。这三个问题搞清楚,方案也就差不多出来了。
我原本以为这只是多开两个 Chrome 窗口。实际跑起来,一个 session 会连到另一个 session 的浏览器;有时关掉一个 Codex,另一个 Chrome 也跟着没了。窗口开了两个,底下的连接却还是混的。
最后的实现很简单:每个 Codex session 使用自己的 CDP 端口和 Chrome profile,启动时再把对应的 MCP 配置传给 Codex。这三项必须成套,不能串。脚本还支持指定或复用 profile,并保留了 Codex 的 resume 能力。
先把连接关系画清楚
CDP(Chrome DevTools Protocol)是 Chrome 的调试和自动化接口。端口就是它的入口。chrome-devtools-mcp 则是 Codex 与 CDP 之间的 bridge,它把“打开页面、执行脚本、读取 DOM”这类工具调用转换成 CDP 请求。
两条会话的连接关系应该是这样的:
Codex session A ──▶ MCP bridge A ──▶ Chrome A
CDP :49101 profile: work-a
Codex session B ──▶ MCP bridge B ──▶ Chrome B
CDP :49102 profile: work-b
这里要隔离的是整条链路。单独多开窗口没有用,MCP、端口或 profile 只要有一处串了,两个 session 还是会互相影响。
先补一个前提:autoConnect 来自 Chrome 官方 MCP
autoConnect 不是这个启动器实现的,它是 Chrome 官方 chrome-devtools-mcp 提供的连接方式。
按照 Chrome 官方文档,Chrome 144 及以上版本可以在 chrome://inspect/#remote-debugging 开启远程调试,然后给 MCP server 加上 --autoConnect:
{
"mcpServers"
:
{
"chrome-devtools"
:
{
"command"
:
"npx"
,
"args"
:
[
"chrome-devtools-mcp@latest"
,
"--autoConnect"
]
}
}
}
这种方式适合让 Agent 接管已经打开的本机 Chrome,也能直接使用现有的登录状态。不过它有一个与多会话有关的前提:如果 Chrome 同时有多个活跃 profile,MCP 会连接 Chrome 判定的默认 profile,并访问这个 profile 下的所有窗口。
这正是我写启动器时要处理的问题。我要的不是“找到一个正在运行的 Chrome”,而是让每个 Codex session 都连到事先为它分配的 profile。于是脚本没有使用 --autoConnect,而是启动独立 Chrome,再通过 --browserUrl 指定连接目标。
MCP 到底注入到哪里
我一开始对“注入”这个词也有点误解。chrome-devtools-mcp 并不会进入 Chrome,它是作为 MCP server 注册到 Codex,然后通过 CDP 去控制 Chrome。
Codex 可以在启动时用 -c 接收这份配置:
codex \
-c
'mcp_servers.chrome-devtools.command="node"'
\
-c
"mcp_servers.chrome-devtools.args=[..., "--browserUrl","
$devtools_url
"]"
第一行告诉 Codex 怎么启动 MCP server。第二行里的 --browserUrl 告诉 chrome-devtools-mcp,它该连接哪个 Chrome。
这也决定了配置应该在什么时候传入。要跑多个 session,就在启动 Codex 时为每个进程传入各自的 browserUrl。这份 MCP 配置只作用于当前 Codex 进程。
profile 不是一个浏览器窗口
第二个问题是 profile。
Chrome 的 profile 本质上是一个可写目录。Cookie、缓存、扩展设置、Service Worker 和登录状态都在里面,Chrome 还会在目录中放锁,防止多个进程同时写入。
启动 Chrome 时,可以用 --user-data-dir 指定这个目录:
chrome \
--remote-debugging-port=
"
$port
"
\
--user-data-dir=
"
$profile_dir
"
如果两个 Chrome 进程共用同一个目录,第二个进程通常会因为锁冲突而启动失败。即便勉强共用了,登录状态和缓存也会混在一起,问题反而更难查。
启动器会给每个 session 分配独立目录。直接运行 ./codex-browser 时,它会用自动选择的端口生成 session 名,比如 codex-49101。这适合临时开一个全新的会话。
如果我想在下次启动时继续使用原来的登录状态,可以固定 CODEX_BROWSER_SESSION:
CODEX_BROWSER_SESSION=work-a ./codex-browser
work-a 会对应一个固定的 profile 目录。当前会话退出后,再用同一个 session 名启动,就能复用里面的 Cookie、登录状态和站点数据。CODEX_BROWSER_PROFILE_ROOT 还可以修改这些 profile 的存放位置,不过一般不需要设置。
复用发生在前后两次启动之间。两个并发会话仍然不能使用相同的 CODEX_BROWSER_SESSION,否则它们会争用同一个目录。
启动器如何连接到正确的 CDP 端口
第三个问题是 CDP 端口。
Chrome 通过 --remote-debugging-port 暴露 CDP,例如:
默认情况下,脚本会向操作系统申请一个当前可用的本机端口。Chrome 启动后,脚本持续请求 /json/version,直到确认 CDP 已经可以访问。日常使用不需要关心具体端口。
CODEX_CHROME_PORT 可以把端口固定下来,但这是高级用法。只有外部工具需要连接一个已知的 CDP 地址,或者需要针对固定端口排错时,我才会设置它。
Chrome 就绪后,启动器已经知道刚刚选中的 $devtools_url,会把它作为 --browserUrl 传给 chrome-devtools-mcp。这一步使用的是官方 MCP 的指定地址连接能力,并不是 --autoConnect。
启动过程可以拆成三步:
-
- 默认自动选择端口,必要时才手动指定;
-
- 启动 Chrome,等待 CDP 就绪;
-
- 把同一个 URL 传进当前 session 的 MCP 配置。
如果要复用一个已经启动的 Chrome,可以传 --browser-url 或 CODEX_CHROME_DEVTOOLS_URL。脚本会先检查对应的 /json/version,确认它确实是可用的 CDP 端点,然后再启动 Codex。
端口可以自动选,选完以后必须明确绑定。
三个参数要跟着同一个 session 走
回头看这三个问题,它们正好对应一条完整的连接:
Codex session ↔ MCP browserUrl ↔ CDP port ↔ Chrome profile
MCP 配置决定 bridge 连接哪个 browserUrl。CDP 端口指向具体的 Chrome 进程。profile 再决定这个 Chrome 使用哪份浏览器状态。
启动器做的事情,就是在启动时把这几项绑到一起。这样每个 Codex session 都有明确的浏览器归属,MCP 也不用临时猜测该连接哪个 Chrome。
谁启动 Chrome,谁负责关
连接隔离以后,还有一个容易被忽略的问题:退出时该关掉哪个 Chrome?
codex-browser 启动 Chrome 后会记录 PID,并注册 trap ... EXIT INT TERM。Codex 退出时,它只关闭自己创建的 Chrome。
如果我传入的是已有 --browser-url,启动器只负责连接,不会关闭那个 Chrome。因为它不是这次启动创建的进程。
端口或 profile 撞车时也应该直接失败。如果脚本偷偷复用一个已经存在的端口,当前 Codex 很可能连到别人的浏览器。这里报错反而更安全,也更容易排查。
我现在怎么启动
最简单的用法就是直接运行:
./codex-browser
要开多个会话,就在不同终端里分别运行同一条命令。每次启动都会自动选择端口,并创建独立的 profile。
脚本也支持恢复 Codex 会话:
./codex-browser r
./codex-browser last
r 会转换成 codex resume,可以选择要恢复的会话;last 会转换成 codex resume --last,直接继续最近一次会话。
如果某个会话以后还要继续用,给它一个固定的 session 名即可。端口仍然交给脚本自动分配:
CODEX_BROWSER_SESSION=work-a ./codex-browser
下一次使用同一个名字,就会打开 work-a 对应的 profile。它也可以和 resume 一起使用,同时恢复 Codex 会话和浏览器状态:
CODEX_BROWSER_SESSION=work-a ./codex-browser r
只有确实需要固定 CDP 地址时,才同时指定端口:
CODEX_CHROME_PORT=49101 CODEX_BROWSER_SESSION=work-a ./codex-browser
这次真正解决的并不是 Chrome 能不能多开。Chrome 本来就能多开。麻烦的是如何让每个 Codex 始终找到属于自己的那个实例,并且在退出时只清理自己的资源。把 MCP 配置、CDP 端口和 profile 放进同一个 session 生命周期里,多个浏览器会话才能稳定地同时运行。
我已经把这个启动器开发好了,代码和使用说明放在 GitHub:agent-browser-launchers。