Skip to content

fix(stepper): report a failed public read as a typed row, not provider text - #4784

Closed
huangruiteng wants to merge 1 commit into
mainfrom
codex/typed-public-read-failure-20260920
Closed

huangruiteng wants to merge 1 commit into
mainfrom
codex/typed-public-read-failure-20260920

Conversation

@huangruiteng

Copy link
Copy Markdown
Collaborator

动机 / Motivation

管家回答里会直接出现 provider 自己的失败原文。这条车道的验收行 todo_386976ec9aae 记下了现场证据:2026-09-13 06:52 / 12:54 的 GitHub 读取返回 Cache miss,2026-09-14 13:20 的 im 读取返回 invalid_arguments,而管家就把这些字串当成「结论」讲给读者 —— 读者看不到「哪个源没读到、影响什么、怎么修」。

根因是一处很具体的实现:loopx/capabilities/issue_fix/github_public.py 把 str(exc) 截断到 180 字符放进 packet 的 read_error 字段(channel probe 与 reply monitor 两处),再被渲染进 markdown;于是 provider 文本成了可被引用的证据。

改动思路 / Approach

把「读失败」从一段 provider 文本变成本行要求的 typed 行:source id + typed reason + coverage effect + next action。新增 github_public_read_failure_row(exc):

  • 按异常类型分类到 6 个 code 之一:provider_timeout、provider_network_unavailable、provider_response_rejected(带 provider_status)、provider_result_unreadable、provider_tool_unavailable、以及兜底 provider_read_failed;这是类型判定,不是子串黑名单。
  • coverage_effect 明确说「这次没读到目标,目标属于未读而不是无活动」——正是本行要求「不得被读成没有进展」的那一条。
  • next_action 给出可执行的修复路径。
  • provider 原文不再进入 packet,只留 provider_message_digest(sha256)供与日志对账:可关联,不可被答案引用。

两个 packet 的字段从 read_error(字符串)改为 read_failure(typed 行),两个 markdown 渲染段从 ## Read Error 改为 ## Unread Source 并列出 typed 字段;死掉的 _compact_error 一并删除。

具体改动 / Changes

  • loopx/capabilities/issue_fix/github_public.py(+116/-15):新增 typed 读失败行与 6 个 code 常量;channel probe 与 reply monitor 都改用它;两个渲染器改为输出 typed 字段;删除 _compact_error。
  • examples/value-connectors-github-public-probe-smoke.py(+26):新增断言 —— 失败行必须是 typed(code/source_id/coverage_effect/next_action/digest)、超时与网络失败分类正确、HTTP 失败带 status,并且 Cache miss 这种 provider 文本不可能出现在行里。

对主干的风险 / Risk

  • 契约变化:packet 字段名从 read_error 改为 read_failure,类型从字符串改为对象。我在仓库内检索过消费方:该字段只在 github_public.py 内部(payload 与两个渲染器)被读取,没有其他调用方或文档引用,所以改名不丢信号;对答案而言这是想要的信号升级(从可引用文本变成 typed 行)。
  • 分类是按异常类型做的:若将来某类 provider 失败换了异常类型,会落到兜底的 provider_read_failed;这仍然是 typed 且有修复指引,不会退回原文泄漏。
  • 明确的范围边界:本 PR 只覆盖行证据里点名的 GitHub 公开读取路径;同一行里 Lark im 读取的 invalid_arguments 属于另一个源,仍留在该行上,不在本 PR 里顺手改。
  • 验证:value-connectors-github-public-probe-smoke ok(含新断言);tests/capabilities 里 issue-fix/value-connector/github 相关 69 passed;ruff 干净;py_compile 干净;loopx canary premerge --from-git-diff 通过(唯一红的 canary 是 main 上既有的 maintainability-ratchet,涉及本改动未触碰的文件)。
  • 控制面改动(loopx/**),按仓库规则由维护者合并,作者不自合并。

English verdict: APPROVE - replaces the provider's raw failure text (previously str(exc) truncated into read_error, which steward answers quoted as "Cache miss"/"invalid_arguments") with a typed read-failure row carrying source_id, one of six exception-type codes, the coverage effect that the target is unread rather than inactive, a repair next_action, and a digest of the provider message for log correlation; both the channel-probe and reply-monitor packets and both markdown renderers were updated and the dead helper removed; the field rename is safe because the key was read only inside that module, and it upgrades what an answer can carry; scope is bounded to the GitHub public read path the row names, with the Lark im read left open on the same row; validated by the value-connectors smoke with new typed assertions (including that "Cache miss" cannot appear), 69 capability tests, ruff and py_compile, and loopx canary premerge --from-git-diff (the single red canary is the pre-existing maintainability-ratchet finding for untouched files); control-plane change, maintainer merge only.

…r text

A failed public GitHub read used to place the provider's own message, truncated
to 180 characters, into the packet's read_error field. Steward answers then
quoted that text instead of naming what was unread: the group shows "Cache
miss" twice on 2026-09-13 and "invalid_arguments" on 2026-09-14, and the
manager evidence row (loopx-meta todo_386976ec9aae) records that each failure
must instead carry source id, typed reason, coverage effect and next action.

Replace the raw text with github_public_read_failure_row: a typed row carrying
schema_version, source_id, one of six reason codes classified from the
exception type (timeout, network unavailable, response rejected with the
provider status, result unreadable, tool unavailable, or a bounded fallback),
the coverage effect that the target is unread rather than inactive, the repair
next_action, and a digest of the provider message for log correlation. Both the
channel-probe and reply-monitor packets and both markdown renderers now carry
the typed row; the raw provider string no longer reaches a payload an answer
can quote. The dead _compact_error helper goes with it.

Scope note: this covers the public GitHub read path the row's evidence names.
The Lark im read that returned invalid_arguments is a different source and stays
open on the same row.

Evidence: value-connectors-github-public-probe-smoke ok with new assertions that
the row is typed, that the coverage effect says unread, and that "Cache miss"
cannot appear in the row; 69 issue-fix/value-connector/github capability tests
passed; ruff clean; py_compile clean; loopx canary premerge --from-git-diff
passed (the single red canary is the pre-existing maintainability-ratchet
finding for files this change does not touch).

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>

@huangruiteng huangruiteng left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

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

审查对象:PR #4784,exact head 21e45ba6bfb95e8ed5ec4828da6b06befe906c84(base main = 361347713)。审查策略 pull_request_review revision 7。

动机

管家回答里会直接出现 provider 的失败原文。本行 todo_386976ec9aae 记下的现场是:2026-09-13 06:52 / 12:54 的 GitHub 读取返回 Cache miss,2026-09-14 13:20 的 im 读取返回 invalid_arguments,而管家把这些字串当结论讲给读者 —— 读者看不到「哪个源没读到、影响什么、怎么修」。

根因具体而窄:loopx/capabilities/issue_fix/github_public.py 把 str(exc) 压空白、截断到 180 字符放进 packet 的 read_error,再渲染进 markdown,于是 provider 文本成了可被引用的证据。这是有界增量:本 PR 只覆盖行证据点名的 GitHub 公开读取路径;同一行里 Lark im 读取的 invalid_arguments 与「把 stale run state 当答案级免责声明」两半仍然开着。

改动思路

按本行要求的四个字段重构读失败:source id + typed reason + coverage effect + next action。

  • 新增 github_public_read_failure_row(exc),按异常类型(不是子串黑名单)分类到 6 个 code:provider_timeout、provider_network_unavailable、provider_response_rejected(带 provider_status)、provider_result_unreadable、provider_tool_unavailable、兜底 provider_read_failed;
  • coverage_effect 明确写「本次没读到该目标,目标属于未读而不是无活动」——正对本行「不得被读成没有进展」;
  • next_action 给出可执行修复;
  • provider 原文不再进入 packet,只留 provider_message_digest(sha256)用于与日志对账:可关联、不可被引用。

字段从 read_error(字符串)改为 read_failure(typed 行),两个渲染段从 ## Read Error 改为 ## Unread Source;死掉的 _compact_error 一并删除,避免留下第二条失败表示路径。

关键代码讲解

  • github_public_read_failure_row(github_public.py:137):分类器 + typed 行构造;注意 HTTPError 必须排在它的基类 URLError 之前判定,否则 403 会被误分类成网络不可用。
  • 两个 packet 构建器的失败捕获(:452 与 :553 附近):由 read_error = _compact_error(exc) 改为 read_failure = github_public_read_failure_row(exc),同时保留 ok=false 与原有的修复型 validation error。
  • 两个 markdown 渲染器的 unread 段(:606、:654 附近):只打印 typed 字段,因此 provider 文本无法再从渲染路径回流进答案。

具体改动

  • loopx/capabilities/issue_fix/github_public.py(+116/-15)
  • examples/value-connectors-github-public-probe-smoke.py(+26):断言 typed 行字段齐全、超时/网络/HTTP(403) 分类正确,并断言 Cache miss 不可能出现在行里。

对主干的风险

最强反例是契约破坏:仓库外的消费者若按字符串读 packet["read_error"],这次改动会让它看不到失败信号。我在仓库内做了全量检索:该字段只在 github_public.py 自身的 payload 与两个渲染器里被读取,没有其他调用方或文档引用;失败证据也没丢,只是换成了更有信息量的名字。若将来发现外部消费者,最小修法是保留一个版本的 read_error 别名 —— 这条我写在评审里,而不是提前加。

其次:分类依赖异常类型,遇到未知类型会落到兜底的 provider_read_failed(仍是 typed 且有修复指引),不会退回原文泄漏。

验证:value-connectors-github-public-probe-smoke ok(含新断言);tests/capabilities 里 issue-fix/value-connector/github 相关 69 passed;ruff 干净;py_compile 干净;loopx canary premerge --from-git-diff 通过。该次 premerge 唯一红的 canary 是 main 上既有的 maintainability-ratchet(chat_server.py、goal_topic_connections.py,本改动未触碰)。

我的整体评价

同意合并(author-owned,以 COMMENTED 记录 APPROVE 结论)。它把「provider 原文当结论」这类回答质量事故在产生点掐掉,换成读者能据以行动的 typed 行,并且刻意不扩张到同一行的另一半。控制面改动(loopx/**),按仓库规则由维护者合并。

English verdict: APPROVE - head 21e45ba; replaces the provider's raw failure text (previously str(exc) truncated into read_error, which steward answers quoted as "Cache miss"/"invalid_arguments") with a typed read-failure row carrying source_id, one of six exception-type codes, the coverage effect that the target is unread rather than inactive, a repair next_action, and a sha256 digest of the provider message for log correlation; both the channel-probe and reply-monitor packets and both markdown renderers were updated and the dead helper removed; the rename is safe because the key was read only inside that module (repository-wide search), and the alternative deprecation alias is recorded rather than added speculatively; scope is bounded to the GitHub public read path the row names, leaving the Lark im read and stale-run-state halves open; validated by the value-connectors smoke with new typed assertions (including that "Cache miss" cannot appear), 69 capability tests, ruff and py_compile, and loopx canary premerge --from-git-diff (the single red canary being the pre-existing maintainability-ratchet finding for untouched files); control-plane change, maintainer merge only.

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.

1 participant