Skip to content

fix(runtime): pin the delegation preview worker's stdio to UTF-8 - #6558

Open
JasonBuildAI wants to merge 1 commit into
loopx-project:mainfrom
JasonBuildAI:codex/delegation-preview-stdio-utf8-z4q9
Open

JasonBuildAI wants to merge 1 commit into
loopx-project:mainfrom
JasonBuildAI:codex/delegation-preview-stdio-utf8-z4q9

Conversation

@JasonBuildAI

@JasonBuildAI JasonBuildAI commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

Goal And Delivered Outcome

Author Declaration

  • Written by: model_agent (DeepSeek V4.1 Flash, OpenCode harness)

Implemented against

Criterion (spec clause) Disposition Symbol / path Test or command
A LoopX-started stdio host pins its own stdin/stdout/stderr to UTF-8 implemented pin_utf8_stdio() as the first statement of serve() in loopx/control_plane/collaboration/delegation_preview_worker.py tests/test_runtime_stdio_host_utf8.py::test_delegation_preview_worker_reads_the_binding_request
A non-ASCII request is read as the caller wrote it implemented the same call; the relayed UTF-8 frame reaches json.loads unchanged, so the bound-work check compares the caller's workspace the same test, in both non-ASCII byte shapes
The real delegation preview transport works under a non-ASCII workspace path implemented worker pin plus the existing transport tests/test_delegation_preview_reuse.py::test_mcp_registration_explicitly_selects_reusable_service with an ASCII and a non-ASCII --basetemp
Input decoding stays strict and a UTF-8 stream is untouched implemented (no change) pin_utf8_stdio keeps stdin strict and skips already-UTF-8 streams existing tests/test_cli_stdio_utf8.py (owner test of the shared helper)
  • Self-check before submission: reused the existing loopx/entrypoint.py::pin_utf8_stdio owner instead of a new helper; read the worker, its transport, its TS bridge and _python_module_command to confirm the pin precedes the first read of sys.stdin and that the caller already writes UTF-8. The refusal path is a read-only availability bug, not an authority bypass: the corrupt decode happens before any authority read or write, and the worker's bound-work check stays intact. Deliberately left out: no transport or bridge change, no retargeting of the bound-work check, no new guard family. A repository scan finds no other LoopX-started module that still reads a UTF-8 stdio transport through the locale codec (the remaining sys.stdin consumers run inside the already-pinned CLI process or read sys.stdin.buffer directly, or are templates/tests). The advisory semantic-inventory probe over the diff reports no new vocabulary carriers. No new dependency and no JSON option change. Left ruff format alone: the worker file is already unformatted on main (verified against the ec327628f blob) and reformatting it would balloon the diff.

Scope And Continuation

  • Completed scope and remaining work: complete within this scope. This worker was the dedicated stdio host module left unpinned by the fix(runtime): pin UTF-8 stdio in four shipped stdio hosts #6472 sweep; both non-ASCII byte shapes are now covered by the family guard.
  • Slice boundary / successor: N/A - the linked issue's expected behavior is implemented and pinned by a regression test.

Validation

  • Tested revision: 55f108387 (content-identical rebase of 5bac3ae49, the head the heavier rows below ran against; rebased onto main at 3bc74f931)
  • Run state: finished
  • Input classes: synthetic
Check kind Result Public-safe evidence / limitation
real_entrypoint passed New case in tests/test_runtime_stdio_host_utf8.py starts the worker exactly as its real caller does ([python, -P, -m], UTF-8 frame on stdin, PYTHONIOENCODING=gbk + PYTHONUTF8=0, cwd = the non-ASCII workspace) in both non-ASCII byte shapes. Whole family file: 12 passed in 7.41s at the tested revision.
real_entrypoint passed Direct probe of the worker at the tested revision: the pre-fix worker exits 1 with ValueError: preview cannot execute or retarget bound work and empty stdout; the pinned worker answers {"kind": "preview", "id": 1, "returncode": 1, "value": {..., "mode": "run_once", "error": "Goal 'goal' is not registered", ...}} for the same frame (the fixture goal is intentionally not registered, so the CLI reports its own structured error).
regression_parity passed Mutation check: reverting only the two-line source change while keeping the test file makes the 2 new cases fail; restoring makes them pass. Recorded during development on both bases (81ff72b64, ec327628f).
integration passed Real transport journey (node bridge + worker, Delegations.inspect): test_mcp_registration_explicitly_selects_reusable_service is 2 passed at the tested revision with both an ASCII --basetemp and one whose directory name contains CJK characters. Before the fix the same non-ASCII run was 2 failed with delegation preview returned no structured result.
integration blocked tests/test_delegation_preview_reuse.py full file on this Windows host: 24 passed, 12 POSIX-skipped, 9 failed. All 9 failures are identical at ec327628f with this diff absent (transport cleanup _process is None assertions; the bridge close does not confirm on this host), so they are pre-existing Windows behavior unrelated to this change. Spot-checked at baseline by name for all 9.
static passed ruff check over the CI scope (tests loopx/canary loopx/control_plane loopx/domain_packs loopx/presentation): All checks passed. ruff format is not run by the workflow; baseline format note above.
static passed scripts/generate_semantic_inventory.py --changed-from HEAD~1: no supported new vocabulary carriers. Public boundary scan of the 2 changed paths: public boundary scan clean: 2 files.
static blocked python -m mypy on this Windows host reports 6 pre-existing errors in files this change does not touch (loopx/extensions/process_runtime.py, loopx/control_plane/effect_runtime.py; POSIX-only attributes such as SIGKILL, killpg, WNOHANG). The configured type-check file list does not include this change; CI runs it on Linux.
  • Coverage and gaps: the changed worker is exercised both directly and through its real bridge/transport, with the locale codec forced in the child so the guard also runs on UTF-8 CI. Untested paths: none identified for this diff - the source change is one import plus one call, and the worker's remaining behavior is untouched. The blocked rows are the pre-existing Windows items named above.

Frontend / Visual Evidence

  • UI impact: none
  • Before: N/A
  • After: N/A
  • States and viewports shown: N/A
  • Source data: none
  • Attention review: N/A - no user-visible surface changed.
  • Craft review: N/A.

Type of Change

  • Bug fix
  • New feature
  • Breaking change
  • Refactoring (no functional changes)
  • Documentation update
  • Test update

LoopX Area

  • Control plane (goals, todos, quota, scheduler, registry, runtime)
  • Benchmark boundary (adapters, runners, verifiers, scoring, evidence)
  • Capability or extension (providers, adapters, skills)
  • Public docs or presentation surface (README, protocols, dashboard)
  • Build, packaging, installer, or CI
  • Host or runtime integration

Technical Direction

Shared-authority RFC fixture impact

  • Production-scale fixture schema: N/A
  • Semantic dimensions changed, or reviewed no-impact rationale: N/A - no typed vocabulary or shared contract changed; the semantic-inventory probe over the diff is empty.
  • Provider conformance arms run: N/A
  • Read-only legacy/file/PostgreSQL three-arm rehearsal (required for promotion, runtime-routing, or compatibility-projection changes): N/A

Boundary Checklist

  • Neither the diff nor this PR body/comments/attachments disclose private state, credentials, raw traces or verifier output, internal links, or local machine paths (including .loopx/, .codex/goals/, and live ACTIVE_GOAL_STATE.md).
  • I did not duplicate maintainer-owned benchmark work unless a maintainer split out a public issue for it.
  • I kept the change scoped to the linked issue/task.
  • I completed the visual evidence section for UI changes, or marked UI impact none.
  • Every commit includes a DCO Signed-off-by trailer (git commit -s).

AI disclosure: this change was drafted with AI assistance at my request, under my direction and review. The constraints for it were set by the human requester (single defect, minimal diff, no unrelated refactor, no new dependency), and a human reviewed the result before submission.

`loopx.entrypoint.pin_utf8_stdio` pins a LoopX-started process's own streams
to UTF-8. loopx-project#6470 found four stdio hosts that answer the same UTF-8 JSON
transport but still read `sys.stdin` and wrote `sys.stdout` with the host
locale codec - `cp936` on a zh-CN Windows host - and loopx-project#6472 pinned them. The
delegation preview worker was another process of that family and was not
covered: `DelegationPreviewTransport` starts it as `[python, -P, -m]` through
`delegation_preview_bridge.ts`, which relays each framed request to its stdin
as UTF-8.

On a cp936 host `gbk` decodes the frame into different text, so a request
whose `--project`/`--scan-root` names a workspace under a non-ASCII path no
longer matches the worker's own cwd and its bound-work check rejects the
still-valid read-only Turn with `preview cannot execute or retarget bound
work`; unpaired bytes raise `UnicodeDecodeError` instead. Worker diagnostics
are not relayed, so the caller only saw `delegation preview returned no
structured result` and delegation inspection was unavailable for those
workspaces - the default layout for a Chinese Windows account, whose home
directory already contains non-ASCII text.

Pin the worker before it reads its first frame and guard it in the existing
`tests/test_runtime_stdio_host_utf8.py` family check. The new test fails on
the previous source and passes with the pin.

Closes loopx-project#6557.

Signed-off-by: JasonBuildAI <jasonbuildai@gmail.com>
@JasonBuildAI
JasonBuildAI force-pushed the codex/delegation-preview-stdio-utf8-z4q9 branch from 5bac3ae to 55f1083 Compare October 15, 2026 17:49
JasonBuildAI added a commit to JasonBuildAI/loopx that referenced this pull request Oct 15, 2026

@loopx-agent loopx-agent 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; model=gpt-6.1-sol; provider=OpenAI; declaration_source=runtime_reported; reasoning_effort=max.

Exact head: 55f1083871930ba4c94b6e483967906b1e85b4b0;实际 merge base 3bc74f9317479e36286414d899a77172a115e330。本评审覆盖完整提案。

动机

在非 UTF-8 宿主上检查委派工作的用户,尤其是工作区路径包含中文时。

旧进程把桥接器发送的 UTF-8 请求按 GBK 解码,真实预览失败;当前头在读取前固定编码,相同 File/SQLite 预览正常返回。

恢复现有只读委派检查,不需要用户改路径或切换整个宿主的编码设置。

本 PR 不改变执行授权、进程清理、前端或 Lark 控件,也不代表所有 Windows 行为都已验收。

改动思路

编码约定继续由已有 pin_utf8_stdio 统一拥有,worker 只在首次读取前调用;TS 仍管进程清理,原 CLI/类型化 owner 仍管读取和权限,没有第二套状态机。

仅修复委派预览 worker 的字节输入适配并补齐现有 stdio 家族回归;不扩大为其他宿主、清理机制或全平台交付。

不再因中文工作区迫使用户迁移目录或重复启动检查;现有 CLI 的只读拒绝、当前状态重读及同一进程后续请求继续成立。 最强的“不发此补丁”方案是保留现状或只设置调用方环境;前者留下已证实故障,后者无法覆盖实际拥有问题的入口。保留现状会继续让正常检查不可用;只在一个调用方导出编码环境会漏掉直接模块入口,并复制已有规则。公共helper本身已符合约定,调用一次即可,无需新命令、设置或TS决策。

具体改动

规范来源:#6557;本次读取正文版本 body-sha256:079ab6295ab5441d6909d62d96e0cede42c49c34b5f0cbd87993959a31c7b550。以下标签对应原文没有编号的Expected behavior条款,不增加新的规范要求。另核对既有 docs/reference/source-cli-entrypoint.md 与实际launcher/helper。

  • UTF8-stdio:implemented。Expected behavior: worker pins its own streams with the existing helper. 由 serve -> pin_utf8_stdio 承担,验证见下。
  • binding-text:implemented。Expected behavior: inspect the same non-ASCII workspace the caller named. 由 serve bound-work checks and real Delegations.inspect 承担,验证见下。
  • strict-input:implemented。Existing source entry contract: malformed input remains rejected; UTF8 streams are untouched. 由 loopx/entrypoint.py::pin_utf8_stdio 承担,验证见下。

完整diff是2个路径 +71/-2:生产代码只增加既有helper的import和serve首条调用;测试保留原cwd默认并新增显式cwd,覆盖两种非ASCII字节形状。没有为了通过检查改宽绑定条件。基线不是“坏JSON被放过”:相同合法UTF8请求在locale解码时失败,当前进入原CLI并返回缺少fixture Goal的结构化错误;真实注册的MCP路径再证明不仅是假回执。两臂实际Python3.11.17和依赖版本已匹配,最初3.14基线留作非决定性历史。

关键代码讲解

  • serve:首帧读取前调用既有UTF8 helper;Goal、Agent、Todo、project、scan-root必须仍与绑定相同,execute/resume继续被拒绝。
  • _run_argv:保留现有默认cwd,同时允许测试显式传入中文工作区;输入和捕获输出均使用字节,避免父进程编码替子进程掩盖问题。
  • test_delegation_preview_worker_reads_the_binding_request:用两种非ASCII字节形状启动真实-P -m worker并读取run_once结构化响应,没有mock提供待证明的CLI结果。

对主干的风险

当前 Python 3.11.17:UTF-8 stdio 家族、helper owner、真实 preview reuse 共60项通过;强制GBK和中文临时目录的真实MCP File/SQLite两项通过,同解释器基线两项失败。独立worker两种名字×GBK/UTF8/改绑/execute/非法字节共10类,每个合法请求连续两帧,当前合法0退出、负例1退出且无文件/权威副作用;基线GBK合法输入失败、UTF8输入保持成功。Ruff通过;native premerge 5 direct+10 selected全部通过,无失败/skip/hold。

审查宿主为 macOS;通过子进程强制 GBK 复现编码边界,未独立执行原生 Windows 整机、已安装 App 或长期 provider 收益。作者另报的九个 Windows cleanup 失败未由本次 Mac 运行重验;不将其当当前已通过。两臂同有 Pydantic forward-reference warning,未改相关源码。

基线拒绝合法GBK场景,当前头正常接受;改绑、execute和非法字节仍被拒绝,临时registry逐字节不变、runtime未产生文件。 本次检查了原始base到head的全部改动、DCO和diff hygiene;已获取的main a99cb07addfc163fda9435c6455cc9462b657855 对这两个原文件的base blob未改变,merge-tree无冲突,但这不是实际组合后运行或安装采用的证明。没有远程CI查询、等待或派发。编码与launcher都只负责IO,原权限、claim/lease及typed effect owner不变;通过premerge也没有得到合并权限。

我的整体评价

我批准这个有明确反例、复用既有owner且完整可逆的修复。long_horizon=improved,user_experience=improved:中文目录下可持续使用原检查,不增加设置或用户准备轮次;本判断不声称长期净收益已实测。未来变更友好性检查已应用:在既有helper接入遗漏worker,没有理由再为两行生产改动抽出新框架。

编码约定继续由已有 pin_utf8_stdio 统一拥有,worker 只在首次读取前调用;TS 仍管进程清理,原 CLI/类型化 owner 仍管读取和权限,没有第二套状态机。 仅修复委派预览 worker 的字节输入适配并补齐现有 stdio 家族回归;不扩大为其他宿主、清理机制或全平台交付。 没有新增共享vocabulary或平行Python决策源;本地/advisory与完整semantic检查均与该边界一致。完整本地premerge通过,没有人工hold;仍需单独执行精确head的closeout/readiness和维护者合并决定。我未合并、升级或修复作者源码。

English verdict: APPROVE - 55f1083; the existing UTF-8 helper repairs the demonstrated GBK/CJK delegation-preview failure while preserving strict input and bound read-only checks; same-Python real File/SQLite baseline/head and local risk checks passed.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

2 participants