Skip to content

fix(issue-fix): re-propose the typed public read failure row (from #4784) - #4928

Merged
huangruiteng merged 1 commit into
mainfrom
codex/typed-read-failure-0923
Sep 23, 2026
Merged

huangruiteng merged 1 commit into
mainfrom
codex/typed-read-failure-0923

Conversation

@huangruiteng

Copy link
Copy Markdown
Collaborator

动机 / Motivation

管家回答里会直接出现 provider 自己的失败原文。GitHub 公开读取失败时,
loopx/capabilities/issue_fix/github_public.py 把 str(exc) 截断到 180 字符放进 packet 的
read_error 字段(channel probe 与 reply monitor 两处),再由 markdown 渲染成
## Read Error。读者看到的是 Cache miss 这类 provider 文本,而不是「哪个源没读到、
覆盖受到什么影响、下一步怎么修」。

这条修复原本在 #4784 提出,于 2026-09-20 被关闭且未合并,之后 main 上
git grep github_public_read_failure 仍为空。因此这里按原样重新提出,不夹带新范围。

改动 / What Changes

  • 新增 github_public_read_failure_row(exc):一条 typed row,带 schema_version、
    source_id、六个 reason code 之一(按异常类型分类:provider_timeout、
    provider_network_unavailable、provider_response_rejected(含 provider status)、
    provider_result_unreadable、provider_tool_unavailable、provider_read_failed 兜底)、
    coverage_effect(目标在这次调用里是 未读到,而不是「没有活动」)、
    next_action(用 gh/已认证工具重试,或显式传入元数据),以及
    provider_message_digest(保留与 provider 日志的相关性,但回答无法引用原文)。
  • channel probe 与 reply monitor 两个 packet 的 read_error 字段改为 read_failure;
    两个 markdown 渲染器改为输出 ## Unread Source 区块。
  • 删除不再使用的 _compact_error。

证据 / Evidence

  • examples/value-connectors-github-public-probe-smoke.py 通过,并新增断言:该行是 typed 的、
    coverage_effect 说明 unread、Cache miss 不可能出现在该行里。
  • tests/capabilities/test_issue_fix_github_encoding.py、
    test_issue_fix_monitor_execution.py、test_issue_fix_candidate_evidence.py:
    49 passed。
  • ruff check 干净;git merge-base --is-ancestor origin/main HEAD 成立(基线 e57b49e)。

边界 / Boundary

control-plane(loopx/capabilities/issue_fix/**),只改公开 GitHub 读取路径的失败表达;
另一位源(Lark im 的 invalid_arguments)仍是同一行里的独立缺口,不在此 PR 范围。
合并决定留给维护者。

…der text

A failed public GitHub read placed the provider's own message, truncated to 180
characters, into the packet's read_error field, so a steward answer could quote
that text instead of naming what was unread. Replace it 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, and the dead
_compact_error helper goes with it.

Scope note: this covers the public GitHub read path. An unread failure from a
different source stays open on the same goal row and is not part of this change.

Evidence: value-connectors-github-public-probe-smoke ok, with new assertions
that the row is typed, that the coverage effect says unread, and that the
provider's own message cannot appear in the row; the focused issue-fix suites
passed (49 cases); ruff clean; the branch is rebased on latest origin/main.

Signed-off-by: huangruiteng <14976749+huangruiteng@users.noreply.github.com>
@huangruiteng
huangruiteng force-pushed the codex/typed-read-failure-0923 branch from b9bdeee to db11094 Compare September 23, 2026 06:47

@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.

动机

这条 PR 是 #4784 的原样重新提出(#4784 于 2026-09-20 被关闭且未合并,之后
git grep github_public_read_failure origin/main 仍为空)。

被修的真实缺口在 problem_context 里是可核验的:main 上
loopx/capabilities/issue_fix/github_public.py 的 _compact_error(exc) 只做
" ".join(str(exc).split())[:180],两个 packet builder 把它塞进 read_error,两个 markdown
渲染器再输出 ## Read Error。也就是说,一次失败的公开读取最终以 provider 自己的字符串
(例如 Cache miss)出现在读者面前,读者看不到「哪个源没读到 / 影响什么 / 怎么修」。

交付判定:这是该验收行(todo_f417292acebb:re-propose or retire)要求的「重新提出」这一半,
而不是整条证据源健康工作的完成 —— 同一行里另一个源(Lark im 的 invalid_arguments)不在
本 PR 范围,PR 内已显式声明边界。

改动思路

入口是 loopx/capabilities/value_connectors/cli.py 调用的两个 packet builder
(build_github_public_channel_probe_packet / build_github_public_reply_monitor_packet),
它们各自 try 一次公开读取,失败时原先记录 provider 文本。

决策边界放在新函数 github_public_read_failure_row(exc):把「失败」从一段不可引用的
provider 文本,变成一条 typed row —— source_id + 6 个 reason code 之一 +
coverage_effect(本次调用未读到该目标,而不是「该目标没有活动」)+ next_action
(用 gh/已认证工具重试或显式传入元数据)+ provider_message_digest。

与既有实现的关系是复用而非另起一层:packet 的 read_error 字段原位改名为 read_failure,
两个渲染器原位把 ## Read Error 换成 ## Unread Source,_compact_error 唯一的两处调用
被替换后直接删除。仓库内没有第二个消费 read_error 的读者(manager_context 里的同名变量
是它自己的 receipt 读取结果,与 packet 字段无关),渲染器也只有
value_connectors/cli.py 的四个调用点,都在新构建的 packet 上。

具体改动

关键代码讲解

  1. github_public_read_failure_row(exc)(新增)—— 这是本 PR 的行为核心。分类顺序先
    subprocess.TimeoutExpired/TimeoutError → provider_timeout,再 HTTPError →
    provider_response_rejected(并把 exc.code 作为 provider_status 带上),
    再 URLError → provider_network_unavailable。这里是正确的:HTTPError 是 URLError
    的子类,如果顺序反过来,401/404 这类「provider 明确拒绝」会被误报成网络不可用。
    之后 JSONDecodeError → provider_result_unreadable、RuntimeError →
    provider_tool_unavailable,其余落到 provider_read_failed,所以没有任何异常类型会
    落回「把原文讲出去」。
  2. provider_message_digest = "sha256:" + sha256(compact) —— 保留与 provider 日志的相关性,
    同时让 answer 无法引用原文。这是把「可相关」与「可引用」分开的关键点。
  3. build_github_public_channel_probe_packet / build_github_public_reply_monitor_packet ——
    read_error: str | None → read_failure: dict | None,validation_errors 的触发条件
    跟着改名,语义不变(仍然是「metadata read failed; retry...」)。
  4. render_github_public_reply_monitor_markdown / render_github_public_channel_probe_markdown
    —— 只在 read_failure 是 Mapping 时输出 ## Unread Source 区块,列出
    source_id / code / coverage_effect / next_action / provider_message_digest;
    非 Mapping 的脏值不会让渲染炸掉。
  5. _compact_error 删除 —— 没有残留调用点,避免了「新 typed 行与旧原文路径并存」的半迁移状态。

对主干的风险

  • 主要风险是 packet 字段改名(read_error → read_failure),而两个 packet 的
    schema_version 字符串仍是 ..._v0:仓库内没有第二个读者,但如果仓库外有人解析
    这两个 packet 或保存了旧 packet 再交给新渲染器,## Read Error 区块会静默消失。这是
    低影响、可接受的(渲染器只在新构建的 packet 上被调用),但属于一次未在版本号上明示的
    字段级变更,建议维护者知悉;最小修补是后续把 packet schema version 提升到 v1。
  • 失败路径的负向走查:fetch_metadata=False(no-fetch 模式)时不会触发这条路径;
    触发时 read_failure 一定非空且 ok=False,packet 仍然返回,调用方可继续用
    --metadata-json,行为与改动前一致。
  • 该改动让回答不再泄露 provider 原文,属于证据表达边界的收紧,不改变任何权限、评分或
    scheduler 行为。

验证

in steward-typed-read-failure-0923(基线 origin/main e57b49e,head
db11094cca40567b6d328759874852256f14361e):

  • uv run --extra test python examples/value-connectors-github-public-probe-smoke.py → ok,
    新增断言覆盖「row 是 typed 的」「coverage_effect 说明 unread」「provider 原文不可能出现在 row 里」。
  • tests/capabilities/test_issue_fix_github_encoding.py、
    test_issue_fix_monitor_execution.py、test_issue_fix_candidate_evidence.py → 49 passed。
  • ruff check loopx/capabilities/issue_fix/github_public.py examples/value-connectors-github-public-probe-smoke.py → clean。
  • 边界扫描:rg 'read_error|read_failure' docs/ 无命中;read_error 在 main 上的其余命中
    都属于 manager_context/TS 的无关同名变量。

结论

APPROVE —— 重新提出的范围与原验收行的要求一致,typed row 取代 provider 原文的核心行为
有 smoke 与正向/负向断言支撑,仓库内无未迁移的读者;唯一需要维护者知悉的是 packet 字段
改名未伴随 schema_version 提升(已作为风险写明,不影响本 PR 合并)。

English verdict: APPROVE - Re-proposes #4784 unchanged and bounded: a failed public GitHub read is now a typed github_public_read_failure_row (source, reason code, coverage effect, repair action, message digest) in both packets and both renderers, with the raw provider text path deleted and no in-repo reader left on the old field; only the un-bumped packet schema_version for that field rename is disclosed as a residual risk.

@huangruiteng
huangruiteng merged commit 6b33ce6 into main Sep 23, 2026
26 of 27 checks passed
@huangruiteng
huangruiteng deleted the codex/typed-read-failure-0923 branch September 23, 2026 10:20
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