最近群里好几个人都在问同一个问题:“Codex 后台任务结束了,为什么还不能直接接着用?”我第一反应是:又一个把“任务跑完”和“会话还在”混在一起的案例。你肯定也遇到过类似场景:终端里用codex exec挂了一个后台重构任务,nohup一挂就是大半天,第二天回来发现任务确实跑完了,日志也有了,产出物也改了,但你想让它顺着刚才的思路继续处理下一批文件时,新开的 Codex 却像失忆了一样,完全不记得刚才在干什么。这篇文章就把这个现象背后的会话机制、常见坑位和可落地的续接方案一次说清楚,适合正在用 Codex CLI、桌面版或 VS Code 插件的开发者,尤其是那些开始把 Codex 接入日常编码工作流、想用后台任务跑长流程的人。
1. 先搞清楚:你的“后台任务”到底跑到哪一步了
很多人在追问“为什么不能接着用”之前,其实并没有确认后台任务在 Codex 的模型里处于什么状态。这一步没搞清楚,后面所有排查都容易跑偏。
1.1 后台任务是怎么被启动的
Codex 的使用方式大致分为两种:一种是直接敲codex进入交互式 REPL,像聊天一样和它来回对话;另一种是一次性执行模式,常见命令是codex exec "任务描述"。后台任务通常属于后者,你会用类似这样的方式启动:
nohup codex exec "分析 src/ 目录结构并输出重构计划" > run.log 2>&1 &或者把它塞进tmux、screen里跑。这种启动方式的核心特点是:codex exec是一个有明确生命周期的进程,它把任务跑完后,进程就会退出,不会像交互式 REPL 那样一直等在那里。也就是说,当你看到“任务结束了”,本质上是“执行任务的 Codex 进程已经结束了”。
这里我踩过一坑:一开始我觉得后台任务既然由codex exec启动,那它结束之后我应该还能回到某个聊天窗口里继续问“你刚才改到哪了”。实际上不存在这个窗口,exec 模式就是一个批处理进程,跑完就退,现场只以数据形式保存在磁盘上。
1.2 任务结束后,现场去哪了
Codex 会把每次会话的完整记录保存成本地文件。以 CLI 为例,默认会写到~/.codex/sessions/目录下,按日期组织,每个会话对应一个或多个 JSONL 文件。里面记录了你的每一条消息、Codex 的回复、工具调用、审批结果等。
也就是说,后台任务结束后,“现场”没有丢,它被序列化成了 JSONL 落盘了。你可以用如下命令看到这些文件:
ls -lt ~/.codex/sessions/*/ | head -20每个文件名的前缀通常就是会话 ID。这个 JSONL 文件才是完整的“对话现场”,但它不会自动出现在你下一次打开的 Codex 窗口里。打个比方:你把一份文档点了保存,关掉了编辑器,文档确实还在硬盘上,但下次打开编辑器时不会自动帮你弹出这份文档。后台任务结束时,Codex 会话就是“已保存但未打开”的状态。
1.3 区分“任务完成”和“会话可续接”
我建议你在排查任何续接问题之前,先建立一个清晰的概念区分:任务完成、进程退出、日志落盘、会话记录存在,这些都不是“会话可续接”的充分条件。
| 状态 | 是否表明可以“直接接着用” | 说明 |
|---|---|---|
| 任务返回码为 0 | 否 | 只代表执行成功,不代表上下文还挂在当前终端 |
| 日志文件有完整输出 | 部分 | 可以人工读日志恢复现场,但 Codex 不会自动读取 |
| 会话 JSONL 文件存在 | 部分 | 说明会话记录已持久化,需要显式的 resume 操作才能加载 |
| 当前终端没有退出 Codex 进程 | 是 | 这种情况才可能直接接着聊 |
| 能通过 resume 列出并选中该会话 | 是 | 这才是“可续接”的真正定义 |
很多用户反馈“不能直接接着用”,不是因为 Codex 把上下文丢了,而是因为他们默认“任务结束了就等于会话还在”。实际上,对 CLI 而言,除非你始终待在交互式 REPL 里没退出,否则任何后台启动的一次性任务结束后,都必须通过会话恢复机制才能接续。
2. 会话管理的底层差异:为什么任务结束不等于会话可续
知道“现场在 JSONL 里”只是第一步。你还得理解 Codex 为什么要把任务和会话拆得这么清楚,以及续接机制本身有什么限制。
2.1 一次调用一个会话:每次 exec 都是独立上下文
Codex 的设计里,codex exec每次调用默认会创建一个新的会话上下文。这样做的好处是并发隔离:你可以同时挂 5 个后台任务,它们各自处理不同模块,不会互相污染上下文;坏处也很明显,就是每次执行结束后,新的交互会话并不会自动继承上一次任务里聊过的任何背景。
如果你在后台任务启动时没有显式指定--session或者使用保持会话的选项,这次调用的会话 ID 就是随机生成的。任务结束、进程退出,这个 ID 确实被记录下来了,但并不会自动成为你下次打开 Codex 时的默认会话。等到你重新敲codex,它只会开启一个全新会话,你自然觉得“它什么都不记得”。
从工程角度看,这种默认行为是合理的。如果每次codex exec都默认合并到同一个长期会话里,后台任务的并发执行、审批状态、上下文压缩都会变得非常不可控。只是这个“合理”,对普通使用者来说有点反直觉。
2.2 续接机制与上下文压缩的副作用
Codex 提供了续接会话的入口。常见做法有两种:
- 在交互模式下,用
codex resume列出历史会话,然后选择一个继续。 - 在执行模式下,用
--continue继续最近一次会话,或者用--session <ID>指定会话。
但这里有个非常实际的限制:上下文窗口是有限的。一个后台任务如果跑了几百轮工具调用,历史记录可能非常长。即便你能把会话加载回来,Codex 也会对早期内容做摘要压缩。说白了,它记得“整体目标”和“刚做完的事”,但中间很多细节可能已经变成概要了。
我实测过很多次,续接回来的会话里,Codex 往往能准确说出“当前正在重构 utils 模块”,但它可能已经忘了某个具体函数当初为什么用Map而不是Record。这种“记得大体、丢了细节”的现象,会让很多人觉得续接体验不佳。这不是 Bug,是上下文压缩的正常结果。
2.3 云端模式与本地模式的差异
Codex 现在还分本地执行和云端执行两种形态。本地模式下,任务的进程、文件读写、工具调用都在你机器上发生;云端模式下,任务可能在一个远程沙箱里跑,本地客户端只负责提交任务、轮询结果。
两种模式下,“后台任务结束”的含义差别很大。本地模式下,进程退出后,你能拿到完整日志和改动文件,JSONL 也同步更新;云端模式下,任务的状态可能由服务端维护,客户端界面展示的是某个异步 Job 的结果,和聊天线程的上下文是两套体系。
尤其在一些桌面版或 Web 版客户端里,你点了“后台运行”按钮之后,这个任务会变成一个独立的 Job,任务完成后界面会告诉你“已完成”,但它并不会把执行过程的上下文合并回你当前的聊天窗口。不同客户端版本的行为还不一样,遇到这种情况,先翻一下当前版本的更新日志,或者直接查 JSONL 会话记录确认上下文落在哪里。
3. 想续接旧任务?这几种方式实测可用
既然理解了背后的机制,下面就是实操环节。我按照“成功率从高到低”的方式,把续接方案整理一下。这些方法在我的日常使用里都验证过,能覆盖绝大多数场景。
3.1 启动前就给会话起名字:最有价值的习惯
如果你提前知道自己要跑一个长任务,并且后续大概率要继续,那强烈建议启动时直接给会话命名:
codex exec --session refactor-phase1 \ "分析 src/ 代码结构,输出重构计划到 PLAN.md" \ > phase1.log 2>&1这样会话 ID 就是refactor-phase1,后续想接续就非常直接:
codex resume refactor-phase1甚至可以直接在 exec 模式里继续:
codex exec --session refactor-phase1 \ "根据 PLAN.md 执行第一步重构,只处理 utils 模块" \ > phase2.log 2>&1指定同一个--session,后续调用就能共享上下文。这是最接近“直接接着用”的方式。注意,不同版本对--session参数的兼容性有些差异,最稳妥的办法是先用codex --help或codex exec --help确认当前版本支持哪些参数。
3.2 用 --continue 续最近一次会话
如果任务启动时没指定会话名,也别着急。Codex 通常会记录“最近一次会话”,你可以用--continue把最近那次任务拉回来:
codex exec --continue "继续刚才未完成的第二、三步"这个方式的适用场景是:后台任务刚结束,你马上就想接着跑,中间没有穿插其他 Codex 调用。一旦你中间跑了别的任务,--continue接的是最新的那次会话,未必是你想续的那个。所以,如果你同时挂了好几个后台任务,我不建议依赖--continue,否则很容易续错。
3.3 从 JSONL 里定位并恢复会话
如果既不记得会话名,也没有立刻--continue,那就直接去~/.codex/sessions/目录里翻。
find ~/.codex/sessions -name "*.jsonl" -mtime -2 | xargs ls -lt找到目标文件后,文件名前缀就是会话 ID,然后:
codex resume <会话ID>这个方法几乎是万能兜底。不过实际排查中你会发现一个问题:同一个任务可能会生成多个 JSONL 文件,因为 Codex 内部可能因为上下文压缩把会话拆成多个 segment。恢复时它会自动处理分段,但如果你在日志里看到的“完整记录”分散在多个文件里,别慌,resume 机制一般会帮你把关联文件接起来。
3.4 用日志和产物重建“足够的上下文”
最极端的情况是:会话文件被清理过、或因为版本升级路径不兼容导致 JSONL 加载失败。这时候想要原封不动续接已经不现实,我的做法是“重建上下文”:
- 把后台任务的
run.log尾部几百行读出来,找到它最后停在哪一步。 - 检查这次任务产生或修改的文件,比如
git diff --stat一眼就能看到动了哪些文件。 - 把结论、当前状态、下一步计划整理成一段简短的背景说明,贴给新会话。
tail -200 run.log git diff --stat这个方法看起来笨,但在跨天、跨版本、跨机器这种极端场景下,它反而是最稳的。会话文件可以丢,但你产出的文档、代码改动、日志还在,拿这些当上下文,新会话一样能顺利接手。
4. 热门坑位逐一排雷:认证、模型、网关、断连
聊完续接,顺手把热搜里高频出现的几个 Codex 报错一次说透。这些坑和“后台任务不能接着用”往往叠加出现,排查的时候要放在一起看。
4.1 认证失效:auth token is unavailable
这个报错在后台任务场景里特别常见。原因通常有几种:
- 登录态过期,token 已经失效。
- 你用的是
nohup启动任务,但定时任务、后台脚本的环境里没有加载你 shell 里的环境变量。 ~/.codex/auth.json的权限不对,Codex 读不到。
排查顺序我建议先看登录状态:
codex login status如果提示未登录,就重新登录。如果是后台环境变量的问题,在启动脚本里显式带上登录后的会话配置,或者直接用codex login完成认证后,再用同一个用户环境去启动后台任务。千万不要在脚本里硬编码 key,不仅容易泄露,还会让排查变得更难:因为你会分不清是环境变量覆盖问题还是 key 本身失效。
4.2 模型与接口不匹配:提示某个模型不受支持
热词里有gpt-5.6-sol这类模型名不支持的报错,常见于你把 Codex 接入了第三方模型服务。Codex 的模型选择是通过配置决定的,如果你在配置里写了一个目标网关不存在的模型名,就会在执行时报错。
解决办法很简单:先确认你的模型提供方到底支持哪些模型标识。不要猜,直接看服务商文档,或者在你的客户端里先发一个测试请求确认模型可用,然后再回到 Codex 配置里改。
如果你是拿 Codex 接入 DeepSeek 这类 OpenAI 兼容接口,需要注意的不只是模型名,还有接口地址和路径要对齐。Codex 默认走的是/responses端点,而不少兼容服务只暴露/v1/chat/completions或/v1/responses,你需要在配置里显式指定完整的 base URL,并在模型配置里使用服务商实际支持的模型标识。建议先小成本验证一次:
curl http://127.0.0.1:端口/v1/models确认返回列表里有你要用的模型,再回头处理 Codex 的配置。
4.3 CC Switch 本地网关失败的排查思路
看到评论区里一堆人在问“CC Switch 本地网关失败,Codex endpoint /responses 报错”,这个我太熟了。很多人用 CC Switch 来统一配置多家模型服务,本质上它会在本机起一个网关服务,Codex 的所有请求都先经过这个本机入口,再被转发到真实服务商。
报错时别急着重装 CC Switch,按这个顺序排查:
| 排查步骤 | 操作 | 说明 |
|---|---|---|
| 1. 看网关是否在运行 | 打开 CC Switch,查看服务状态和端口 | 本地网关进程没起来,Codex 当然连不上 |
| 2. 确认端口被监听 | lsof -i :端口号或 `netstat -an | grep 端口号` |
| 3. 核对 Codex 配置 | 打开~/.codex/config.toml,检查 base URL 是否指向网关端口 | 经常有配置里还残留官方地址的情况 |
| 4. 核对端点路径 | 确认网关暴露的是/v1/responses还是/responses | 路径不匹配就会在 /responses 上报错 |
| 5. 重启顺序 | 先启动网关,等端口就绪,再启动 Codex | 顺序反过来,Codex 会缓存连接失败结果 |
我见过最多的坑就是第 4 步。很多人配了 base URL,但没注意到网关实际暴露的是/v1/responses,而 Codex 请求路径是/responses,差一个前缀就整个挂掉。手动在浏览器或 curl 请求一次网关地址,看到真实返回路径,再回头改代码配置,问题就清楚了。
4.4 请求超时与假死:request timed out
后台任务跑一半报request timed out,也算高频问题。Codex 在交互模式下对单次请求有比较严格的超时控制,而复杂任务里模型思考时间长,或者本地网关转发慢,就容易触发超时。
处理策略我一般分三种:
- 短任务超时:重试一次即可,多半是瞬时网络抖动。
- 长任务超时:把任务拆小,不要在一条指令里塞太多要求。
- 批量任务频繁超时:切换到 exec 模式,别用交互模式,因为 exec 模式对任务整体执行时间的容忍度更高。
另外,出现“看起来卡住”的情况时,先别急着杀进程。用ps aux | grep codex看进程状态,再配合tail -f run.log看日志是否还在增长。日志在涨就说明任务还活着,只是某个请求响应慢;日志完全不涨,才考虑杀掉重启。
5. 安装、桌面版与插件协同的避坑清单
热词里还有一大片是安装、桌面版、插件相关的内容。这些坑虽然和会话续接没有直接关系,但它们会影响你后续排查问题的基础环境。
5.1 Windows 安装与桌面版登录问题
Windows 上装 Codex,最常碰到的是安装包装好了,但codex命令在终端里找不到。这通常是 PATH 没刷新,重开一个终端基本能解决,不行就手动把安装目录加进系统 PATH。
桌面版打不开或者登录不上,我建议先去日志目录翻错误信息,桌面版一般都有日志文件。常见的登录问题其实是系统时间和证书校验导致的,时间不对,认证请求会被服务端拒绝,表现就是界面一直转圈或者提示登录失败。你先校准系统时间,很多时候问题就消失了。
5.2 VS Code 插件、Codex Skill 与 CLI 的会话互通性
VS Code 插件虽然和 CLI 共用同一套登录配置,但它们的会话列表不一定互通。插件里维护的会话历史,可能在 CLI 的codex resume列表里看不到。所以我的建议是:一个任务只在一个工具里跑。你既然在 VS Code 插件里开启了一个后台任务,续接就继续在插件里操作,不要切到 CLI 去resume,那样容易找不到会话。
Codex Skill 也一样。技能本身是放在~/.codex/skills下的配置,CLI 和插件都能读取。但后台任务执行时,如果某个 Skill 的输出特别长,会占用大量上下文空间,导致后续续接时压缩更激进。建议后台任务的提示词里,尽量缩小 Skill 的作用范围,让输出变得短小精悍。
5.3 多工具混用时的身份与会话冲突
桌面版、CLI、VS Code 插件并存时,它们可能共用同一个登录文件,但同时发起多个任务会带来两个问题:一是多个进程同时刷新同一个 token 文件,可能互相覆盖;二是不同工具的会话索引策略不一样,同一个任务在 A 工具里能 resume,在 B 工具里完全找不到。
我自己现在固定一套规矩:
| 场景 | 工具 | 备注 |
|---|---|---|
| 快速问答、代码解释 | CLI 交互模式 | 不挂后台,问完即走 |
| 长任务、批量重构 | CLI exec 模式 | 带--session命名,配日志 |
| 边看代码边改 | VS Code 插件 | 跟随编辑器上下文 |
| 多模型切换 | CC Switch + CLI | 固定端口,不频繁切换 |
这套规矩执行下来,跨工具找会话的冲突基本消失了。
6. 我最常用的后台任务+续接工作流
最后分享一套我自己长期在用的方案。它不是官方最佳实践,但实测下来稳定,适合任务会被拆分、跨天执行、需要频繁续接的情况。
6.1 固定会话名 + 进度状态文件
后台任务启动时,我一定指定会话名,并且会让任务把结论写入一个状态文件。比如跑重构任务:
codex exec --session refactor-main \ "分析 src/ 目录,完成重构计划并更新 TASKS.md,最后一条记录当前进度和下一步动作" \ > run.log 2>&1强制它写TASKS.md非常关键。这样即便后续 JSONL 加载失败,我打开TASKS.md就能知道它做到哪一步了。你可以把状态文件当作“项目的会话锚点”,这个概念比依赖 Codex 的自动记忆可靠得多。
6.2 阶段续接的标准姿势
每一天开始续接任务时,我一般这样做:
# 看状态文件 cat TASKS.md # 把状态贴给新会话或在原会话里续接 codex exec --session refactor-main \ "根据 TASKS.md 的当前进度,继续执行下一步,完成后更新 TASKS.md" \ >> run.log 2>&1这里用的是同一个会话名,所以 Codex 能读取到之前的上下文。但同时因为我每次都让它更新TASKS.md,即使会话加载不顺利,新会话也能借助状态文件快速接手。双保险比单保险稳得多。
6.3 任务结束前强制“交接”
我在每个后台任务的提示词里都会加上一句:“任务结束后,把当前状态、已完成的改动、下一步计划写入STATE.md。”效果立竿见影。
以前我总依赖 Codex 的自动记忆,结果发现跨天的长任务经常出现“它记得目标但忘了细节”的情况。后来改成强制写交接文档,续接的稳定性大幅提升。说到底,Codex 的会话机制更像是一个文档系统,而不是一个永远在线的大脑。你能做的就是把这个文档用好:命名、保存、翻出来、必要时自己补一段摘要。
我在实际使用中最大的体会是:与其纠结“后台任务结束了为什么不能直接接着用”,不如换个角度——每次后台任务启动之前,就把它当成一次“有明确产物、需要交接”的工程来做。指定会话名、输出日志、更新状态文件,三件事加起来不超过一分钟,却能让后续所有续接都变得可控。Codex 的新版本还在不断调整会话管理行为,但不管它怎么改,把现场落盘、把进度写清楚这个思路永远不过时。