你在一个回合中途断了 SSH,重新连上后执行 codex resume。等来的不是对话,而是一行冷冰冰的提示:thread 019a4c1e-... already has an active writer。会话没有损坏,也没有丢数据。Codex 只是在告诉你:还有另一个进程握着这条会话的写入权。
这是协调层面的错误,跟账号无关,也跟模型无关。它有明确的成因、可定位的持有者,以及一条能解决问题的命令。下面讲清楚机制、怎么找出占着会话的进程,以及当那个进程在另一台机器上时该怎么办。
"already has an active writer" 是什么意思?
Codex 允许每条会话只有一个写入者,这个约束由操作系统的文件锁来保证。每条会话都有自己的锁文件:$CODEX_HOME/thread-writer-locks/<thread_id>.lock,未设置 CODEX_HOME 时默认在 ~/.codex 下。
取锁是非阻塞的。后到的进程不排队,直接拿到报错:
thread 019a4c1e-... already has an active writer
源码里的路径很明确:进程对文件调用 try_lock,如果返回 WouldBlock,就原样抛出这条消息(codex-rs/rollout/src/writer_lock.rs)。它不是网络超时,不是额度,也不是登录问题,而是一把本地锁还被人握着。
为什么我关了终端,锁还占着?
因为进程没有死。关掉终端窗口、断掉 SSH 连接,或者用 Ctrl+Z 挂起,都不会结束 Codex,而锁只有等进程真正退出才会释放。
| 场景 | 谁占着会话 | 相关记录 |
|---|---|---|
| SSH 断开时 TUI 处于空闲 | 留在服务器上的孤儿 TUI 进程 | openai/codex #44063 |
| 同一会话同时开在桌面端和 CLI | 先打开会话的桌面端 | openai/codex #37403 |
| 通过 Remote Control 在 iOS 应用里查看过该会话 | 为 Remote Control 服务的 app-server 守护进程 | openai/codex #44449 |
三种情况形状相同:一个没人关掉的旧会话。哪一种都不需要重装任何东西。
怎么找出占着会话的进程?
会话 id 就在报错信息里。拿到它,用 lsof 指向对应的锁文件:
lsof ~/.codex/thread-writer-locks/<thread_id>.lock
输出第二列就是 PID:
COMMAND PID USER FD TYPE DEVICE SIZE/OFF NODE NAME
node 48213 mafra 23u REG 0,45 0 912 /home/mafra/.codex/thread-writer-locks/019a4c1e-....lock
如果你的 CODEX_HOME 指向别的目录,把路径换成 $CODEX_HOME/thread-writer-locks/。没装 lsof 时,对同一个文件执行 fuser -v 能得到同样的答案。
怎么在不丢历史记录的前提下释放会话?
结束握着锁的进程,然后重新恢复会话:
kill 48213
codex resume 019a4c1e-...
不需要删掉 .lock 文件。锁属于操作系统,进程结束的那一刻就释放了。手动删文件反而制造第二个问题:旧进程还活着、还在往同一条会话里写,于是同一个 rollout 上出现两个写入者,而这正是这把锁本来要防的事。
如果锁的持有者是 app-server 守护进程,用它自己的命令,而不是 kill:
codex app-server daemon stop
如果进程在另一台机器上呢?
那就杀不了任何东西。能做的是读取:在较新版本的 TUI 里,resume 撞上这把锁时不会直接失败,而是以只读模式打开会话,输入框被禁用。
在这个模式下,按 f 会复制这条会话,且不申请原会话的锁。副本会拿到新的 id,把历史记录一起带过去,并且可以正常写入。当你够不到那台占着会话的机器时,这就是继续干活的路子。
代价要说清楚:原会话仍然是锁住的,副本成为活跃对话。等你之后重新拿到那台机器的访问权,记得结束旧进程,免得留下两条分叉的历史线。
怎么避免锁再次把会话卡住?
一条会话,一个客户端。锁之所以存在,是因为两个界面同时写同一段对话会产生不一致的历史,所以实操规则就是别把同一条会话同时开在两个地方。
- 在 CLI 里恢复之前,先关掉开着这条会话的桌面应用。
- 离开远程会话时,先用
/exit退出 Codex 再断开 SSH。关终端不会结束进程。 - 要并行干活,用会话副本,而不是在同一会话上开两个窗口。
- 如果你用 Remote Control,先在桌面端恢复之前确认手机应用里没有留着这条会话。
几个快问快答
删掉 .lock 文件能解决吗?
不能。锁属于进程,不属于文件。持有者还活着时删掉文件,会让两个写入者同时写一条会话,而这正是锁要防的问题。
只有一条会话报错,其他都正常,为什么?
因为锁是按会话分配的,不是按安装实例。一个孤儿会话只卡住那一段对话,Codex 的其余部分照常工作。
这是我环境里的 bug 吗?
不一定。仓库里公开的记录显示,Linux 配 SSH、macOS 配桌面端加 CLI、以及 iOS 上的 Remote Control 都出现过同样的行为。这是跨界面的会话协调问题,不是配置坏了。
如果你把同一条会话开在三个客户端里,只是为了轮换着用、别把额度跑爆,那最短的路是只用一个 CLI。Verboo Code 提供不限量的 token,用 /model 就能换模型,没有按 token 计费,也就没有理由在同一会话上挂两个客户端。了解 Verboo Code。



