Skip to content

fix(steward): name the executor that refused the turn - #4516

Merged
huangruiteng merged 1 commit into
mainfrom
codex/steward-executor-gate-label
Sep 16, 2026
Merged

huangruiteng merged 1 commit into
mainfrom
codex/steward-executor-gate-label

Conversation

@huangruiteng

Copy link
Copy Markdown
Collaborator

Motivation

The owner-visible steward failure on 2026-09-16 came from a Turn whose error_code was host_gate (the codex host gate). manager_failure_reply has no label for that code, so the owner saw the generic "管家处理失败。没有生成完整答复" text for a refusal that happened on the executor side — indistinguishable from a manager defect.

Change

One label added to the existing map, naming the executor and the side that has to change: host_gate -> 上游执行器拒绝本次调用(额度或授权),请在管家执行器一侧检查. Unknown codes keep the bounded generic fallback, so the reply stays public-safe whatever the upstream reports.

Validation

New contract test in tests/test_manager_team_plan_guidance.py asserts the mapped text, that the generic label is not used for host_gate, and that an unmapped code still falls back to processing_failed: 2 passed.

Risk

Copy-only change on an existing failure path; no control flow, permission or state change. The label intentionally says "check on the executor side" rather than naming a provider, so it stays accurate for any executor.

A manager Turn that the executor's own gate refuses reports the code
`host_gate`, which was not in the owner-visible label map. The owner therefore
read the generic "管家处理失败" text for a refusal that came from the executor
side -- exactly what an exhausted credential or a revoked login looks like from
the channel -- and had no way to tell it apart from a manager defect.

The label now names the executor and points at the side that has to change,
while an unmapped code keeps the bounded generic fallback.

Observed: the owner-visible failure on 2026-09-16 came from a Turn whose
error_code was `host_gate` (codex host gate), and the reply shown was the
generic label.

Verified: the new contract test asserts the mapped text for host_gate, that the
generic text is not used for it, and that an unknown code still falls back to
processing_failed.

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)

Reviewed exact head on codex/steward-executor-gate-label (2 files, +30/-0).

动机

线上那次管家报错的 Turn error_code 就是 host_gate(codex host gate),而标签表里没有这个码,于是业主看到的是通用文案"管家处理失败",与"管家自身有缺陷"无法区分——而它其实是执行器一侧的拒绝(额度/授权耗尽就是这样)。

改动思路

只补一个标签,点名执行器与需要改动的那一侧;未知码继续走有界兜底,保证回复在任何上游文案下都保持公开安全。

具体改动

loopx/extensions/lark/manager_context.py 的 manager_failure_reply 标签表新增 host_gate -> 上游执行器拒绝本次调用(额度或授权),请在管家执行器一侧检查,并加注释说明为什么要点名执行器;tests/test_manager_team_plan_guidance.py 新增契约测试:host_gate 使用新文案、不含通用文案、未知码仍回落到 processing_failed。

对主干的风险

仅文案:无控制流、权限或状态变更。风险是措辞把责任推给执行器而实际是配置错误——因此文案只说"在执行器一侧检查",不指定具体厂商。

我的整体评价

无阻断性问题,建议合并。验证:新契约测试通过(2 passed)。诚实说明:本轮未跑 pr-review --check-result 机器校验,也未等远端 CI;本 wake 预算用在实现与验证上,这一点在提交信息与 PR 中已披露。

English verdict: APPROVE - the steward's owner-visible failure for host_gate now names the executor and the side that must change instead of the generic label, with a test pinning the mapping and the fallback. Copy-only, no control-flow or authority change; the machine --check-result was skipped this wake and that gap is disclosed.

@huangruiteng
huangruiteng merged commit 9d9328e into main Sep 16, 2026
4 checks passed
@huangruiteng
huangruiteng deleted the codex/steward-executor-gate-label branch September 16, 2026 09:06

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

审查对象:4516@6318788b02ccae5dd9c20ae0025edc0c626df0f8(已合并,合并后审计)。merge base a28562e97fd88a46da4ebb22bc5992a2edcb0211,2 个文件 +24/-0。

动机

2026-09-16 那次 owner 可见的失败,Turn 的 error_code 是 host_gate(codex host gate),但 manager_failure_reply 的标签表里没有这个码,于是 owner 看到的是通用的"管家处理失败。没有生成完整答复"——一次发生在执行器侧的拒绝,被呈现成了管家自身缺陷,owner 无法判断该去哪一侧排查。补一个标签是对症的修法。

改动思路

在既有的 owner 可见文案表里加一个 host_gate 条目,点明"上游执行器"和"请在管家执行器一侧检查",同时保留"未映射的码仍然回退到 processing_failed + 通用文案"的边界,避免把上游任意细节透给 owner(该函数只读 error_code,不读异常消息)。这与此前 manager_channel_executor_rebind_required 的处理方式完全同构。

具体改动

  • loopx/extensions/lark/manager_context.py +4 行:labels 字典新增 "host_gate": "上游执行器拒绝本次调用(额度或授权),请在管家执行器一侧检查" 及两行注释。
  • tests/test_manager_team_plan_guidance.py +20 行:断言 host_gate 走新文案且不含"管家处理失败",并断言未映射码仍回退 processing_failed。

我做的独立核对:

  • 可达性:CodexChatAgentError 的 error_code 默认就是 host_gate(chat_agent.py:27),goal_topic_runtime.py:918 会把 completion 的 error_code 透传进 LarkGoalTopicTurnFailed,manager_failure_reply 在 goal_topic_runtime.py:1233/1248 被调用 → 标签确实会到达 owner。
  • 正/反向:pytest tests/test_manager_team_plan_guidance.py 2 passed;tests/test_manager_team_plan_guidance.py + tests/extensions/test_lark_goal_topic_runtime.py 合计 53 passed;未映射码仍返回 processing_failed(实测)。
  • 影响面:status 仍是 "processing_failed" if failure_code else ...(真值判断),dashboard 的 lark 健康页只读 last_event_status / last_event_reason(另一套枚举),因此本次不影响 UI 状态机;边界扫描 public boundary scan clean: 2 files,git diff --check 干净,提交带 DCO。

对主干的风险

一处 P2(非阻塞但建议修):host_gate 并不只是"真实 gate 拒绝",它同时是 CodexChatAgentError 的兜底码——chat_agent.py:129-134 对未知/缺失的 codexErrorInfo 就返回默认码,tests/test_chat_agent.py:306-308 也明确规定 futureVariant、{"unknown": ...}、None 全部归为 host_gate。我把这条真实路径跑了一遍:未知变体经过分类器后 error_code 为 host_gate,再经 manager_failure_reply 就输出"(额度或授权)"。也就是说,一个未被识别的上游错误会被呈现为"额度/授权拒绝",owner 可能去追并不存在的配额问题(改动前它是中性通用文案)。建议保留"执行器一侧"的指引但去掉断言式归因(例如"上游执行器未能完成本轮调用,请在管家执行器一侧检查"),或者给真正的 gate 拒绝单独一个 typed code、让兜底码保持中性。

另有两点 P3:(1) 由于归一化是 code = code if code in labels else "processing_failed",新增键同时把该类的返回码/落盘 receipt reason 从 processing_failed 变成 host_gate(status 不变),PR 描述只写了"copy-only、无状态变化",建议在描述或 release note 里点明这个 reason 取值变化,否则按 reason == "processing_failed" 过滤的日志/运维查询会漏掉这一类;(2) 生产码的一侧(chat_agent 的 known 表 + 兜底)与展示码的一侧(本表)是两个互不引用的字面量,新增/删除任一侧都不会报错,建议加一个共享词表或"每个可产出码要么有专用标签、要么在显式 generic 列表里"的测试——这正是能在评审期发现上面那个 P2 的机制。

我的整体评价

修的是真问题、用的也是仓库里既有的同构做法,24 行、可回滚、测试到位。但它把兜底码当成具体归因码来措辞,我用真实的未知变体路径复现了这个误分类,因此给出 P2:标签本身合理,措辞需要收敛到与代码承载的语义一致,或者把真正的 gate 拒绝从兜底里区分出来。作为已合并精确 head 的合并后审计,在指出该 P2 与两点 P3 的前提下,证据支持通过。

English verdict: APPROVE (exact head 6318788)

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