Skip to content

fix(runtime): retire acquired children when capped startup fails - #8359

Merged
huangruiteng merged 1 commit into
mainfrom
codex/process-startup-cancellation-20261108
Nov 8, 2026
Merged

huangruiteng merged 1 commit into
mainfrom
codex/process-startup-cancellation-20261108

Conversation

@loopx-agent

Copy link
Copy Markdown
Collaborator

Goal And Delivered Outcome

run_capped_process can leave a child running when its constructor or I/O-thread setup is interrupted after a PID is available. That setup currently precedes its cancellation cleanup guard.

This change retains the Popen object before initialization and covers initialization and thread startup with the existing process-tree retirement path. It closes every created pipe, joins only started threads, and preserves the original exception when retirement succeeds. The function signature, process-group flags, timeout policy, output bounds and result fields remain unchanged.

  • Outcome basis: a reproducible shared-runtime cancellation defect; intended base main.
  • Before → after: the same synthetic startup suite has 9 failing regressions / 3 passing controls with the baseline owner, then 12 passing checks with this change. All test-owned children are cleaned up even in the baseline failure replay.

Author Declaration

  • Written by: model_agent — OpenAI GPT-6.1 Sol.

Implemented against

  • Specification and revision: the existing loopx/extensions/process_runtime.py::run_capped_process contract at 4e949f405528fd8087f0623526c62f890c1ce982, with the PID-known startup-cancellation defect described above as the maintainer-agreed repair scope. No separate written specification.
Criterion Disposition Owner / decisive check
Keep an acquired child owned through constructor and stream/thread setup failures implemented Existing run_capped_process retirement path; real-child constructor-unwind, signal and partial-thread-start tests
Do not mistake unstarted threads or absent PIDs for acquired resources implemented Before-launch rejection and partial-setup tests
Preserve normal input, environment, working directory, exit and output-limit behavior implemented Existing process/runtime and extension entrypoint tests plus ordinary-launch controls
Keep cleanup failures visible implemented An unconfirmed retirement remains an exception; it is never converted to a successful result

Scope And Continuation

Complete for startup failures after the native PID is published on the Popen object. Merely allocating that object does not cover a lower-level fork/launch interruption before PID publication; that boundary remains unconfirmed. This proposal does not claim recovery after forced owner death or retirement of independent process groups. It adds no signal handler, permission, execution flag or new default.

The bounded refactor consolidates resource setup under its existing lifetime owner and includes stdin in the same final cleanup. A separate cancellation owner or broader process framework is unnecessary for this slice. Python remains the OS transport adapter; no control-plane decision ownership moves.

Validation

  • Tested revision: 4782f7c725a86396e31a61a8858482114aeb549d.
  • Run state: finished.
  • Input classes: synthetic.
Check kind Result Public-safe evidence / limitation
regression_parity passed Same tests/extensions/test_process_runtime_startup.py: baseline 9 failed / 3 passed, candidate 12 passed. Baseline c263f7c78353fcaa914f23475d4d99d07eab7c01 has the same owner blob as the current PR base.
unit, real_backend passed python -m pytest -q tests/extensions/test_process_runtime_startup.py tests/extensions/test_process_runtime.py tests/test_process_runtime_stdin_deadline.py tests/test_process_runtime_loading.py: 109 passed, 1 skipped on macOS/aarch64 with both CPython 3.11.17 and 3.14.8. The skipped case is Windows-only junction behavior. These tests launch real owned subprocesses; no model host is invoked.
real_entrypoint, regression_parity passed Existing extension install/run, capability execution, stdout/stderr overflow and timeout paths: 5 passed on baseline and candidate, using the same synthetic provider fixtures and real subprocess backend.
static passed Ruff F checks on both changed files; mypy on the process owner; git diff --check; RFC bilingual diff check (no RFC changed); loopx check public-boundary scan with an isolated empty registry.

Coverage limits: Windows execution is left to CI; the lower-level pre-PID native-launch boundary is outside this qualification. No installed-runtime, benchmark outcome or performance-benefit claim is made.

Frontend / Visual Evidence

  • UI impact: none. Existing callers retain the same runtime API; no frontend, Lark or CLI operation is added.

Type of Change

  • Bug fix
  • Test update

LoopX Area

  • Capability or extension (providers, adapters, skills)
  • Host or runtime integration

Technical Direction

S4 runtime/host cancellation and stop reliability; this scoped repair does not close the whole stream.

Shared-authority RFC fixture impact

N/A. No state schema, authority-store behavior or provider routing changes.

Boundary Checklist

  • No private state, credentials, raw trajectories, internal links or local machine paths are included.
  • No maintainer-owned benchmark work is duplicated.
  • The change stays within the reproduced runtime defect.
  • No paired RFC changed; bilingual diff check passes.
  • UI impact is none.
  • The commit carries a DCO Signed-off-by trailer.

Signed-off-by: LoopX Agent <337587101+loopx-agent@users.noreply.github.com>

@huangruiteng huangruiteng left a comment •

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=max

Approval conclusion (author-owned PR; GitHub blocks formal self-approval)

该标题保留 capability packet 的 author-owned fallback 标签。本条实际由 GitHub 账户 huangruiteng 发表为 COMMENTED,正文记录独立模型的 APPROVE 结论;未宣称 GitHub 原生 APPROVED,也未执行合并。

未发现此 exact head 的阻断性问题。APPROVE 仅针对下面的 PID-known 独立切片,不是主干合并或任何活跃运行的放行。

动机 / Motivation

使用 extension run、capability executor 或隔离 view validator 的调用者会遇到这个问题。当子进程已取得 PID、启动初始化或 I/O thread setup 随后被取消时,旧路径抛出异常却留下可执行进程;新路径先执行原有回收再返回异常。独立实测证明已知 PID 的子树和已启动线程会被回收,普通结果、完整错误及拒绝顺序保持不变。本次不承诺 PID 发布前的原生启动窗口、强制杀死 owner、独立进程组或活跃运行的整体恢复。剩余边界是 PID 尚未发布时的 native launch 中断;Windows 原生执行也未在本机验证。

这不只是修改测试断言:我在 immutable base 4e949f4 通过真实 capability executor 重现“外层收到 execution failed、内部 child 仍运行”。相同输入在 head 4782f7c 的完整错误没有变化,但 child 与全部已创建管道已清理。未发现单独已接受的 startup-cancellation 书面规范;评审在 diff 前阅读 base 的既有资源所有权合同、实际调用者与公开 scope,不把本 PR 新测试当作规范或权限来源。

改动思路 / Approach

资源获取和失败清理必须属于同一个既有 lifetime owner;这里复用 terminate_process_tree,不另建取消策略或授权层。当前 PR 仅闭合 PID 已发布后的初始化与 I/O thread setup 失败;pre-PID 原生启动窗口不在此资格内。只把普通 Popen(...) 包进 try,仍无法在 constructor 已取得 PID 又抛错时取回对象,context manager 也尚未进入;另建 supervisor 或 signal framework 会扩大契约。因此先保留对象,再将初始化和 stream/thread setup 纳入原 guard,是此已证问题的较小修复。没有修改工作策略、permission、超时值或调用者 API;Python 仍是既有 OS transport adapter。

最强的反对理由是 pre-PID 窗口仍然存在,而且显式 new/init 比普通构造更依赖标准库生命周期。我据此独立验证 CPython3.11.17/3.14.8,没有把可用切片夸大成所有启动取消都安全。旧 timeout/output cleanup 与新增 startup cleanup 共用一个 owner,stdin 也进入同一 finally;这是同域收敛,没有新增持久状态或兼容分支。

具体改动 / Changes

生产代码仅一文件 +59/-47,净+12;另一文件为192行真实进程回归测试。全 diff 已检查,没有 schema、CLI、配置、权限或 agent instruction 增加。现有调用者包括 standalone/capability runtime 和 isolated presentation validator。

关键代码讲解

  • run_capped_process:初始化前保留 Popen 对象,原 constructor 参数、进程隔离、timeout/output bounds 与结果结构保持。取得 PID 后的初始化异常现在进入既有 except;pid 缺失只表示 adapter 没拿到它,不能证明底层没有创建 child。
  • terminate_process_tree:既有 OS 回收实现未变,新路径复用它。我的反例让 leader 已 exit0、同组 grandchild 忽略 TERM;不能用 leader.poll() 或管道 EOF 冒充整组停止。head 实际停止该 grandchild,旁边独立组 sentinel 继续运行。
  • partial stream setup regression:child、stream、thread 的真实状态断言先于 teardown,测试自己的兜底回收不计作产品成功。我另测第三个 stdin writer 已启动后抛错;base 仍有 leader 和三个线程,head 均回收。finally 只 join 已开始的 thread,并关闭包括 stdin 的全部已创建 pipe。

同一 frozen startup suite 在 exact base 是9 FAIL/3 PASS,head 是12 PASS,由本次独立重跑取得,没有从旧 ancestor 或作者声明继承。另经 execute_extension_runtime_binding 做六类完整观察:normal、empty request、invalid JSON、invalid timeout 与 oversized request 重叠、missing program、PID-known startup failure。全部 result/error(包括 cause 类型及正文)base/head 严格相等,未做语义 normalization;唯一有意改变是最后一类 child 从仍活着变为已回收。既有 Unicode/input/env/cwd、非零退出、无执行 deadline、stdout/stderr overflow 与 timeout 合同也通过。

对主干的风险 / Risks

独立检查 结果和边界
head 四组 process/loading suites CPython3.11与3.14各109 PASS/1 Windows-only SKIP
exact base/head 同 startup fixture 9 FAIL/3 PASS →12 PASS;旧失败资源均由测试兜底回收
installed synthetic extension install/run、capability、输出溢出及timeout base/head各5 PASS;真实本地 subprocess,无模型或账户
exited leader/live grandchild、第三 writer、未启动PID control base前两类失败,head3/3;无关peer未终止
Ruff F、mypy owner、diff whitespace、RFC pair及两文件公共边界扫描 PASS;没有RFC变化

语义与 CI 对齐

复用 existing OS ownership vocabulary,没有新 schema 或双重决策 owner。正常返回和完整错误合同保留,不以 stricter behavior 或测试数量本身代替兼容证明。本修复没有 opt-in/default-off 声明;正常、preview、disabled 和参数拒绝路径已通过 base/head 实际 caller 核对,没有因“feature对象缺省”就推断隔离。必要资源清理也没有降成 optional observer。

遵循 Goal 的 wait_for_ci=false,没有 fetch、poll 或等待 CI,不推测未观察的 CI 根因。Windows native execution 未验证,不能称跨平台全绿。cleanup 本身失败时仍保留异常和原 context,不返回成功。重复强杀 owner、独立进程组以及 native fork/launch 到 pid publication 前的窗口仍在本切片外;本评审不为外层 controller 或现有任务的整体恢复背书。没有安装、promote、启动科学任务或修改活跃状态。

我的整体评价 / Verdict

这是 justified_increment:已证明的 PID-known 泄漏在真实入口消失,降低反复失败时的资源堆积风险;未测量长期业务成果、模型成本或分数。正常用户路径与错误读回保留,无额外设置或确认轮次。未改变渲染页面、消息或 CLI 展示;检查的调用路径为 extension install/run、capability executor 与 isolated view validator。无视觉变更;Windows 原生执行、安装版本及活跃运行的端到端恢复未验证。

当前代码量与失败后果相称,既有 owner 收敛及 stdin cleanup 的 bounded refactor 已应用;不需要为这个切片新建进程框架或先解决全部 native-launch 问题。剩余 gap 应由原 native launch/OS owner 在需要更强保证时取证,不能把未发布 PID 伪装成“没有child”,也无需为本评审虚构 Todo。APPROVE 不授予 main merge;发表前再次读取 head,任何 head 变更须重新资格。

English verdict: APPROVE — 4782f7c. Independent real-process and base/head checks validate the PID-known startup cleanup slice. Pre-PID native launch, forced owner death and native Windows execution remain outside this qualification.

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Reviewer: model_agent; gpt-6.1-sol; OpenAI; runtime_reported; reasoning_effort=max

Independent approval review

未发现此 exact head 的阻断性问题。APPROVE 仅针对下面的 PID-known 独立切片,不是主干合并或任何活跃运行的放行。

动机 / Motivation

使用 extension run、capability executor 或隔离 view validator 的调用者会遇到这个问题。当子进程已取得 PID、启动初始化或 I/O thread setup 随后被取消时,旧路径抛出异常却留下可执行进程;新路径先执行原有回收再返回异常。独立实测证明已知 PID 的子树和已启动线程会被回收,普通结果、完整错误及拒绝顺序保持不变。本次不承诺 PID 发布前的原生启动窗口、强制杀死 owner、独立进程组或活跃运行的整体恢复。剩余边界是 PID 尚未发布时的 native launch 中断;Windows 原生执行也未在本机验证。

这不只是修改测试断言:我在 immutable base 4e949f4 通过真实 capability executor 重现“外层收到 execution failed、内部 child 仍运行”。相同输入在 head 4782f7c 的完整错误没有变化,但 child 与全部已创建管道已清理。未发现单独已接受的 startup-cancellation 书面规范;评审在 diff 前阅读 base 的既有资源所有权合同、实际调用者与公开 scope,不把本 PR 新测试当作规范或权限来源。

改动思路 / Approach

资源获取和失败清理必须属于同一个既有 lifetime owner;这里复用 terminate_process_tree,不另建取消策略或授权层。当前 PR 仅闭合 PID 已发布后的初始化与 I/O thread setup 失败;pre-PID 原生启动窗口不在此资格内。只把普通 Popen(...) 包进 try,仍无法在 constructor 已取得 PID 又抛错时取回对象,context manager 也尚未进入;另建 supervisor 或 signal framework 会扩大契约。因此先保留对象,再将初始化和 stream/thread setup 纳入原 guard,是此已证问题的较小修复。没有修改工作策略、permission、超时值或调用者 API;Python 仍是既有 OS transport adapter。

最强的反对理由是 pre-PID 窗口仍然存在,而且显式 new/init 比普通构造更依赖标准库生命周期。我据此独立验证 CPython3.11.17/3.14.8,没有把可用切片夸大成所有启动取消都安全。旧 timeout/output cleanup 与新增 startup cleanup 共用一个 owner,stdin 也进入同一 finally;这是同域收敛,没有新增持久状态或兼容分支。

具体改动 / Changes

生产代码仅一文件 +59/-47,净+12;另一文件为192行真实进程回归测试。全 diff 已检查,没有 schema、CLI、配置、权限或 agent instruction 增加。现有调用者包括 standalone/capability runtime 和 isolated presentation validator。

关键代码讲解

  • run_capped_process:初始化前保留 Popen 对象,原 constructor 参数、进程隔离、timeout/output bounds 与结果结构保持。取得 PID 后的初始化异常现在进入既有 except;pid 缺失只表示 adapter 没拿到它,不能证明底层没有创建 child。
  • terminate_process_tree:既有 OS 回收实现未变,新路径复用它。我的反例让 leader 已 exit0、同组 grandchild 忽略 TERM;不能用 leader.poll() 或管道 EOF 冒充整组停止。head 实际停止该 grandchild,旁边独立组 sentinel 继续运行。
  • partial stream setup regression:child、stream、thread 的真实状态断言先于 teardown,测试自己的兜底回收不计作产品成功。我另测第三个 stdin writer 已启动后抛错;base 仍有 leader 和三个线程,head 均回收。finally 只 join 已开始的 thread,并关闭包括 stdin 的全部已创建 pipe。

同一 frozen startup suite 在 exact base 是9 FAIL/3 PASS,head 是12 PASS,由本次独立重跑取得,没有从旧 ancestor 或作者声明继承。另经 execute_extension_runtime_binding 做六类完整观察:normal、empty request、invalid JSON、invalid timeout 与 oversized request 重叠、missing program、PID-known startup failure。全部 result/error(包括 cause 类型及正文)base/head 严格相等,未做语义 normalization;唯一有意改变是最后一类 child 从仍活着变为已回收。既有 Unicode/input/env/cwd、非零退出、无执行 deadline、stdout/stderr overflow 与 timeout 合同也通过。

对主干的风险 / Risks

独立检查 结果和边界
head 四组 process/loading suites CPython3.11与3.14各109 PASS/1 Windows-only SKIP
exact base/head 同 startup fixture 9 FAIL/3 PASS →12 PASS;旧失败资源均由测试兜底回收
installed synthetic extension install/run、capability、输出溢出及timeout base/head各5 PASS;真实本地 subprocess,无模型或账户
exited leader/live grandchild、第三 writer、未启动PID control base前两类失败,head3/3;无关peer未终止
Ruff F、mypy owner、diff whitespace、RFC pair及两文件公共边界扫描 PASS;没有RFC变化

语义与 CI 对齐

复用 existing OS ownership vocabulary,没有新 schema 或双重决策 owner。正常返回和完整错误合同保留,不以 stricter behavior 或测试数量本身代替兼容证明。本修复没有 opt-in/default-off 声明;正常、preview、disabled 和参数拒绝路径已通过 base/head 实际 caller 核对,没有因“feature对象缺省”就推断隔离。必要资源清理也没有降成 optional observer。

遵循 Goal 的 wait_for_ci=false,没有 fetch、poll 或等待 CI,不推测未观察的 CI 根因。Windows native execution 未验证,不能称跨平台全绿。cleanup 本身失败时仍保留异常和原 context,不返回成功。重复强杀 owner、独立进程组以及 native fork/launch 到 pid publication 前的窗口仍在本切片外;本评审不为外层 controller 或现有任务的整体恢复背书。没有安装、promote、启动科学任务或修改活跃状态。

我的整体评价 / Verdict

这是 justified_increment:已证明的 PID-known 泄漏在真实入口消失,降低反复失败时的资源堆积风险;未测量长期业务成果、模型成本或分数。正常用户路径与错误读回保留,无额外设置或确认轮次。未改变渲染页面、消息或 CLI 展示;检查的调用路径为 extension install/run、capability executor 与 isolated view validator。无视觉变更;Windows 原生执行、安装版本及活跃运行的端到端恢复未验证。

当前代码量与失败后果相称,既有 owner 收敛及 stdin cleanup 的 bounded refactor 已应用;不需要为这个切片新建进程框架或先解决全部 native-launch 问题。剩余 gap 应由原 native launch/OS owner 在需要更强保证时取证,不能把未发布 PID 伪装成“没有child”,也无需为本评审虚构 Todo。APPROVE 不授予 main merge;发表前再次读取 head,任何 head 变更须重新资格。

English verdict: APPROVE — 4782f7c. Independent real-process and base/head checks validate the PID-known startup cleanup slice. Pre-PID native launch, forced owner death and native Windows execution remain outside this qualification.

@huangruiteng
huangruiteng merged commit a8454cd into main Nov 8, 2026
26 of 37 checks passed
@huangruiteng
huangruiteng deleted the codex/process-startup-cancellation-20261108 branch November 8, 2026 17:06
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.

2 participants