Files
codex-app-server/docs/thread-resolve-fix.md
host 5fd4e23b68 fix: thread/unarchive 走已归档解析(--archived 列表)
probe9 实测:thread/archive 200 后 thread/unarchive 稳定 404。
根因是 #103 时代的逻辑 bug:unarchive 走普通解析,普通
thread-list 不传 --archived 时只回非归档 thread,刚归档的
thread 永远解析不到。修法只改服务端:_list_task_threads 加
archived 模式(传 --archived,home×provider 穷尽不变),
_resolve 与 _pick_latest_thread 加归档开关,_task_thread_mutate
加 resolve_archived 参数只供 unarchive;rename/archive/compact
仍走非归档解析。生产只读验证:同一 worktree 普通列表 0 条、
archived 列表恰好 1 条(probe9 归档的 thread),其 archived
字段为 None。
2026-10-28 06:10:02 +08:00

4.3 KiB
Raw Permalink Blame History

线程解析修复:provider 过滤 + 共享 home 回退

故障(生产实测 2026-10-28)

本地任务的 thread rename/archive/unarchive 稳定 404 thread not found,thread/read 回 200 但 thread 为 null。 而同一任务的 worktree、bindings、快照、心跳、移交全部正常。

定位(证据链,全部在生产主机上实测)

  1. host-state 里有该任务的 thread_id,cwd 与服务端推导的 worktree 路径一字不差;rollout 文件的 session_meta cwd 也是同一路径;state_5.sqlite 的 threads 表里有该行 (cwd 正确、archived=0、最新)。
  2. 不带 provider 注入的 app-server 进程调 thread/list: 全量翻页(cursor 可用)共 77 条,探针 thread 不在其中; 带 cwd 过滤时回空;页内已知 thread 的 cwd 过滤正常回 1 条。
  3. 区分点是 provider:列出的 77 条全是 xcmapi,探针两条 是 sub2api;带 --model-provider sub2api 注入的进程调 同一 cwd 过滤,立刻回 1 条(正是探针 thread),无过滤 首页即含它。
  4. 第二个错位:服务端一直查租户 home (tenants/<项目>/codex-home),本地 worker 按 host-config-host.json 用共享 home (/var/lib/codex-app-server/codex-home),旧 thread 全在后者。

结论:app-server 按进程 provider 过滤 thread/list,且 服务端只查租户 home——两个错位叠加,本地任务解析必 404。 不是分页 cap(翻页后仍无)、不是 cwd 记错、不是 home 丢数据。

修法(只改服务端,不动派发)

cloud/server.py:

  • _run_host_cli 新增 model_provider 参数,拼在子命令 之前(host.py 顶层参数,拼进 argv 会被子命令解析器拒绝)。
  • _list_task_threads 按序穷尽,首个非空即停: home:租户 home → 本地 worker 共享 home (_local_worker_codex_home,只读 host-config-host.json 的 codex_home 一键,缺失/解析失败时跳过); provider:缺省 → sub2api(仅当 server 进程配了 SUB2API_BASE_URL,否则 host.py loud fail,不能变 502)。 任一候选成功回空即停(空就是空);全部报错才 502 首错。
  • _resolve_task_thread 返回 provider,后续的 thread-read / rename / archive / unarchive 原样带 provider——同一 thread 在另一 provider 进程里同样看不到,丢了它改操作会 502。
  • _task_thread_read(#103 的富化 read)此前解析后又用 租户 home 调 thread-read,已改成解析出的 home + provider。

不改派发的原因:本地 worker 改租户 home 会丢共享 home 里的 skills 等内容,有回归风险;且服务端兜底已覆盖全部 现存 thread。租户隔离(本地任务共用一个 home)是另案, 不在本批修。

未覆盖

  • Docker 任务仍 409(thread 在容器内,服务端查不到是设计)。
  • _task_threads 列表端点同样走 _list_task_threads,兜底 对它同样生效;但它不翻页,只返回解析到的该任务 thread。
  • 真 PR 创建仍未实弹(与本批无关)。
  • 新测试 9 个:cloud/tests/test_thread_resolve.py (CLI 参数位置、共享 home 读取、回退顺序、空→404、 host.py 失败→502)。

后续:unarchive 归档解析

探针 probe9 发现:thread/archive 200 后 thread/unarchive 稳定 404。根因是 #103 时代的逻辑 bug:unarchive 走普通 解析,普通 thread-list 不传 --archived 时只回非归档 thread,刚归档的 thread 永远解析不到。修法(只改服务端):

  • _list_task_threads(task, archived=False):archived 为真 时传 --archived,只列已归档 thread;home×provider 穷尽 回退不变。生产只读验证:同一 worktree 普通列表 0 条、 archived 列表恰好 1 条(即 probe9 归档的 thread),且其 archived 字段为 None。
  • _resolve_task_thread(task, archived=False) 与 _pick_latest_thread(threads, include_archived=False): 归档模式下不跳过显式 archived 条目。
  • _task_thread_mutate(..., resolve_archived=False):只供 unarchive;rename/archive/compact 仍走非归档解析。
  • 新测试 4 个:cloud/tests/test_thread_unarchive.py (--archived 透传、普通解析 404 而归档解析命中、pick 归档开关、unarchive 调 CLI 时解析模式与 provider 正确)。