Skip to content

fix(turn): resume preserved sessions on failed-turn retry - #3262

Merged
huangruiteng merged 1 commit into
huangruiteng:mainfrom
ObVious55:codex/gh-c92-timeout-recovery
Aug 16, 2026
Merged

fix(turn): resume preserved sessions on failed-turn retry#3262
huangruiteng merged 1 commit into
huangruiteng:mainfrom
ObVious55:codex/gh-c92-timeout-recovery

Conversation

@ObVious55

Copy link
Copy Markdown
Contributor

Summary

  • mark Codex CLI timeouts as resumable only after an opaque session has been observed and preserved
  • reconcile eligible failed-Turn retries from the journaled start_new request to resume
  • validate the current Goal/Agent/Todo session binding before retrying the Host
  • keep identity drift fail-closed without additional Host execution or durable side effects
  • support atomic session persistence on platforms where os.fchmod is unavailable

Root cause

When a Codex CLI Turn timed out, LoopX preserved the observed session binding.

However, --resume-turn-key --retry-failed-turn reloaded the original journaled plan, where the session action was still start_new. The Codex adapter then detected the preserved binding as session drift and rejected the retry before invoking the Host.

As a result, a recoverable timeout could not actually resume the preserved session.

Behavior change

Failed-Turn reconciliation is now enabled only when all of the following are true:

  • the caller explicitly requests retry_failed
  • the failure occurred during host_execute
  • the Host recorded typed resume_session recovery metadata
  • a fresh session binding exists
  • its Goal, Agent, and Todo identities match the failed Turn

When these conditions hold, the retry request is changed from start_new to resume.

Missing or mismatched bindings fail before another Host invocation.

Measured results

Recovery invariant Before After
Eligible timeout recovery succeeds 0/1 1/1
Preserved session is resumed 0/1 1/1
Host action sequence start_new only start_new → resume
Retry Host calls on identity drift 0 0
Duplicate durable writebacks 0 0
Duplicate quota charges 0 0
Persistence without os.fchmod 0/1 1/1

The successful recovery path performs exactly one retry Host invocation, one successful durable writeback, one quota spend, and one scheduler transition.

Safety and scope

  • timeouts that occur before a session is observed are not marked resumable
  • ordinary non-recovery session-drift validation remains unchanged
  • recovery metadata is typed and limited to the host_execute phase
  • Goal/Agent/Todo identity drift remains fail-closed
  • no benchmark scoring, task semantics, permission boundary, or submission behavior changes
  • no raw sessions, trajectories, logs, credentials, or private state are added

Validation

  • python -m pytest -q tests/test_loopx_turn_executor.py tests/test_loopx_turn_driver.py
    • 80 passed
  • Ruff checks passed
  • LoopX public-boundary checks passed
  • git diff --check passed

Implements GH-C92.

Closes #3228

@ObVious55
ObVious55 marked this pull request as draft August 16, 2026 12:29
Signed-off-by: ObVious55 <ningk6@mail2.sysu.edu.cn>
@ObVious55
ObVious55 force-pushed the codex/gh-c92-timeout-recovery branch from 8e36348 to e3bed82 Compare August 16, 2026 12:31
@ObVious55
ObVious55 marked this pull request as ready for review August 16, 2026 12:32

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR #3262 全量双语评审 — Resume Preserved Sessions on Failed-Turn Retry

精确评审头(Exact Head): 3262@e3bed8223077d9ef1229e76b6436e77104560a02
Base: main | 类型: runtime behavior(6 files, +362/-8) | 作者: ObVious55


详细中文评审

动机

Codex CLI Turn 超时后,LoopX 已把观察到的 session binding 持久化;但 --resume-turn-key --retry-failed-turn 会重放原始 journaled plan,其 session action 仍是 start_new,Codex adapter 因此把“已保留的 binding”判为 session drift 并拒绝重试——可恢复的超时实际无法恢复。PR 让 failed-Turn 重试在验证当前 Goal/Agent/Todo binding 后,把 start_new 请求对账为 resume,并保持 identity drift fail-closed。

改动思路

把“可恢复 host 失败”显式接入 turn journal:BuiltInHostError 新增 recovery_kind="resume_session"(仅当超时时观察到并保留了 session),_run_host_runner 把它带进 host observation,_host_result_stage 写入 journal["host_recovery"];重试时 _reconcile_failed_turn_retry_request 校验 schema/kind、要求 failed_phase=host_execute、解析当前 binding,再经 reconcile_failed_turn_session_requeststart_new 替换为 resumebinding_status=failed_turn_recovery)。任何绑定不匹配都在执行 host 之前抛错,不产生额外 host 执行或 durable 副作用;_store_codex_cli_session 同时兼容无 os.fchmod 平台(temp 文件 + os.replace + chmod 兜底)。

具体改动

  • loopx/cli_commands/turn.py(+14/-2):为 codex-cli host 注入 session_binding_resolvercodex_cli_session_binding)。
  • loopx/control_plane/turn_driver/codex_cli.py(+11/-3):超时且观察到 session 时抛 recovery_kind="resume_session";session 存储兼容无 fchmod 平台(fd 关闭逻辑修正)。
  • loopx/control_plane/turn_driver/driver.py(+39):新增 reconcile_failed_turn_session_request——用当前 binding 重新 _session_plan,要求 action=resume,把 journaled start_new 重写为 resume 并打 failed_turn_recovery 标记。
  • loopx/control_plane/turn_driver/executor.py(+67/-3):HOST_RECOVERY_* 常量、BuiltInHostError.recovery_kind、host observation 透传、journal 写入/清理、_reconcile_failed_turn_retry_requestrun_loopx_turn_once 接入(重试前对账,成功后清 host_recovery)。
  • 测试(+231):CLI 端到端(start_new → resume、恢复后 status=stopped、spend 0)与 executor 级(host/writeback/spend/scheduler 计数、identity drift 拒绝、fchmod 缺失回退)。

关键行为讲解

  1. recovery_kind 只在“观察到 session”时设置:无 session 的超时仍走普通失败,不会伪造可恢复路径。
  2. 重试对账:先校验 host_recovery schema/kind 与 failed_phase=host_execute,再解析当前 binding;start_newresume 重写,resume 保持原样,action 非法则拒绝。
  3. fail-closed:binding 与 goal/agent/todo 不符时在 host 执行前抛 ValueError(测试断言 host 只调用 1 次、writeback/spend 均为 0)。
  4. 原子持久化os.fchmod 不可用时先写 temp、os.replace、再 chmod 600;fd 关闭逻辑避免泄漏。

正向路径

Turn 超时 → 观察到并保留 session binding → journal 记 host_recovery=resume_session → 重试解析 binding → 请求改 resume → host 继续同一 session → 正常结算(executor 测试:host 2 次、writeback/spend/scheduler 各 1 次)。

负向路径

无 session 超时 → 无 recovery_kind → 不重写;binding 漂移 → host 执行前拒绝且无副作用;schema/kind 非法或 failed_phase 不符 → 拒绝;fchmod 缺失 → atomic 存储回退。

对主干的风险

无阻断项。 commit e3bed8223Signed-off-by(Sign-off check pass);GitHub build/dependency-review pass,pytest 评审时刻 pending。exact head 上 test_loopx_turn_driver.py + test_loopx_turn_executor.py 80 passed,覆盖本 PR 全部行为面。

非阻断(P2):合并前确认全量 pytest 变绿;host_recovery 是新增 journal 协议字段,建议在 release notes 注明 failed-Turn 恢复语义。

验证矩阵(exact head 实测)

场景 结果
test_loopx_turn_driver.py + test_loopx_turn_executor.py 80 passed
commit sign-off 存在 → Sign-off check pass
GitHub checks build/dependency-review pass;pytest pending
改动范围 6 files, +362/-8,turn driver/executor/CLI

我的整体评价

根因定位准确:journaled plan 的 start_new 与已保留 binding 冲突导致可恢复超时无法恢复。修复把 recovery 作为一等 journal 状态接入,重试时先对账后执行,identity drift 在 host 调用前 fail-closed,且补了无 fchmod 平台的原子持久化——实现收敛、测试覆盖正反路径。无阻断项。结论:APPROVE


English Verdict

Verdict: APPROVE — exact head 3262@e3bed8223077d9ef1229e76b6436e77104560a02.

Summary: A focused fix that lets failed-turn retries resume a preserved Codex CLI session: timeouts with an observed session now record a typed host_recovery, and --retry-failed-turn reconciles the journaled start_new request to resume against the current Goal/Agent/Todo binding, failing closed on identity drift before any host execution or durable effects. Session persistence also handles platforms without os.fchmod. 80 focused tests pass at the exact head; Sign-off, build, and dependency-review are green, with pytest pending at review time.

@huangruiteng
huangruiteng merged commit df4604a into huangruiteng:main Aug 16, 2026
5 of 6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Codex CLI timeout recovery cannot resume the session observed by the failed Turn

2 participants