From fcb2af100213b1fc0832e62254649c21148f6577 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Yuang=20Panwang=20=28=E6=BD=98=E7=8E=8B=E9=9B=A8=E6=98=82?= =?UTF-8?q?=29?= Date: Tue, 28 Jul 2026 22:57:01 +0800 Subject: [PATCH] fix(hooks): fail loud on Codex dispatch regression --- CHANGELOG.md | 19 ++++++++++-- INSTALL.md | 20 ++++++++++++- README.md | 2 ++ md-swarm/SKILL.md | 2 +- md-swarm/preflight.py | 9 ++++-- md-swarm/probe_live_hooks.py | 30 +++++++++++++++++-- ...14\345\205\250\346\211\213\345\206\214.md" | 6 ++-- 7 files changed, 75 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e314000..6d6ae5e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,11 +7,24 @@ ## [未发版 / Unreleased] +## [v0.11.2] - 2026-07-28 + +### Codex Hook 调度回归:响亮停机 + 文档勘误 + +*(EN: fail loudly on the upstream Codex hook-dispatch regression; correct v0.11.1's overstatement and stop reinstall/restart loops.)* + +- current-session live-probe 失败时明确硬停写入型 `md-swarm` / `md-iterate`,并链接 OpenAI 上游回归 [openai/codex#21639](https://github.com/openai/codex/issues/21639)。 +- Codex 已显示 Active/Trusted 但真实动作仍落盘时,不再循环建议重装、Trust、重启;改为切换到已通过双 DENY 的宿主。 +- `probe_live_hooks.py --selftest` 新增失败指引回归断言,防止未来重新引入“重启循环”。 +- README、INSTALL、用户手册与 v0.11.1 发布说明同步澄清:v0.11.1 修复的是事件形状适配,不能修复宿主未调度 Hook 的上游故障。 + ## [v0.11.1] - 2026-07-28 ### Codex Desktop 工具形状兼容 + Claude runner 加固 -*(EN: recognize Codex Desktop's real apply_patch/shell_command events while preserving Claude Code's PowerShell hook layer through the shell-neutral runner.)* +*(EN: recognize Codex Desktop's apply_patch/shell_command payload shapes when the host dispatches hooks, while preserving Claude Code's PowerShell hook layer through the shell-neutral runner.)* + +> **2026-07-28 勘误:** 本版本修复的是 Codex 事件形状/参数适配,不能修复 Codex Desktop 自身未调度 Hook 的上游回归。物理保护是否可用只认 current-session live-probe 的两个明确 DENY;详见 v0.11.2 与 [openai/codex#21639](https://github.com/openai/codex/issues/21639)。 - Codex policy 兼容 FREEFORM `apply_patch` 与 `shell_command`,并用真实事件形状补回归测试。 - Codex 安装 matcher 同步覆盖动态工具名;live-probe 与 preflight 改为跨 harness 提示。 @@ -65,6 +78,8 @@ - 锁定版 pandoc 工具链一键安装(pandoc 3.9.0.2 + pandoc-crossref 0.3.24a,版本配套、自动下载)。 - 附:中文用户完全手册、Apache-2.0 许可、NOTICE 第三方声明。 -[未发版 / Unreleased]: https://github.com/pwya/md-paper/compare/v0.11...HEAD +[未发版 / Unreleased]: https://github.com/pwya/md-paper/compare/v0.11.2...HEAD +[v0.11.2]: https://github.com/pwya/md-paper/compare/v0.11.1...v0.11.2 +[v0.11.1]: https://github.com/pwya/md-paper/compare/v0.11...v0.11.1 [v0.11]: https://github.com/pwya/md-paper/compare/v0.1...v0.11 [v0.1]: https://github.com/pwya/md-paper/releases/tag/v0.1 diff --git a/INSTALL.md b/INSTALL.md index 37d5168..5afcd31 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -94,6 +94,14 @@ The installer deploys one shared policy core to `~/.md-paper/hooks/md_hook_polic - **OpenCode**: installs `~/.config/opencode/plugins/md-paper.js`, using `tool.execute.before` to call the same policy core. - **Hermes / other agents**: no adapter yet; use the rules below. +> **Known Codex upstream regression (current as of 2026-07-28):** recent +> Codex Desktop and some non-interactive CLI builds can discover a hook, show it +> as Active/Trusted, yet never dispatch the handler. See +> [openai/codex#21639](https://github.com/openai/codex/issues/21639). Registration, +> Trust, restart, and a passing policy `--selftest` are not session-level proof. +> For write-heavy `md-swarm` / `md-iterate`, only two explicit DENY results from +> the live probe count. + These hooks are **layer 2**. Layer 1 remains load-bearing: single-writer apply + citation/uniqueness/order gates. Codex's own documentation also describes current `PreToolUse` interception as a guardrail rather than a complete enforcement boundary, so keep [AGENTS.md](AGENTS.md) in every paper project: 1. The rules in [AGENTS.md](AGENTS.md) — Codex / OpenCode / Hermes read that file automatically as workspace instructions. @@ -128,6 +136,16 @@ For the session-level proof, run `md-swarm/probe_live_hooks.py --prepare`, perfo the two printed real tool calls, and require both to be explicitly **DENIED**. The filesystem `--check` result alone is not sufficient. +If either real action lands: + +1. Stop all write-heavy md-paper work in that harness. +2. If the adapter is missing or untrusted, install/review it once and open a + fresh session. +3. If `/hooks` already shows Active/Trusted, **do not loop reinstall, Trust, or + restart**. Treat it as the upstream dispatch regression, switch to a harness + that passes both DENY checks (currently Claude Code is recommended), and + retest Codex only after its runtime or hook configuration actually changes. + **OpenCode:** restart it (local plugins load at startup), then check the same core: ```powershell @@ -161,7 +179,7 @@ Run Steps 1–5 above yourself in PowerShell, substituting the cloned repo path - **Claude Code** — skills auto-load, plus the established protection hooks (layer 2). - **OpenCode** — reads `~/.claude/skills` natively; the installed local plugin supplies a best-effort layer-2 guard. -- **Codex** — junction into `~/.codex/skills`; `hooks.json` supplies a best-effort layer-2 guard after `/hooks` trust review. +- **Codex** — junction into `~/.codex/skills`; `hooks.json` supplies a conditional best-effort layer-2 guard after `/hooks` trust review. Current Desktop builds may not dispatch trusted hooks at all ([upstream #21639](https://github.com/openai/codex/issues/21639)); require two live DENY results before write-heavy use. - **Hermes Agent** — junction into `~/.hermes/skills` (Step 2). Compatible with the agentskills.io standard; supports `AGENTS.md` workspace instructions. - **Any other agent** — the five `SKILL.md` files are plain Markdown runbooks and every pipeline step is an ordinary CLI script (`py md-swarm\apply_md_changeset.py …`, `powershell md-build\build.ps1 …`). Tell the agent to read the relevant `SKILL.md` and follow it. - **Safety model everywhere**: the load-bearing protection is **layer 1, inside the scripts** — `apply_md_changeset.py` is the single writer of `manuscript.md`, with citation / uniqueness / order gates. Harness hooks/plugins are an additional best-effort layer 2; keep the [AGENTS.md](AGENTS.md) iron rules in front of every agent. diff --git a/README.md b/README.md index 6e897df..19e96af 100644 --- a/README.md +++ b/README.md @@ -131,6 +131,7 @@ flowchart LR ### 已知限制 - **仅支持 Windows + Microsoft Word** —— `md-unpack` 靠 Word COM,macOS 暂不支持。 +- **Codex Desktop 的 Hook 调度存在上游回归** —— 当前部分 Codex Desktop / 非交互 CLI 版本会显示 Hook 已 Active/Trusted,却不真正执行处理器([openai/codex#21639](https://github.com/openai/codex/issues/21639))。在 Codex 中运行写入型 `md-swarm` / `md-iterate` 前,必须亲眼看到 current-session live-probe 两个动作都 DENY;否则切换到已通过验证的宿主,当前推荐 Claude Code。 - *其他细微限制(引用来源、页码定位、个别转义、浮动图、老 AxMath 公式)未在此列出,见[用户完全手册](md-技能套件·用户完全手册.md)。* ### 许可 @@ -282,6 +283,7 @@ flowchart LR ### Known Limitations - **Windows + Microsoft Word only** — `md-unpack` drives Word via COM; macOS is not supported yet. +- **Codex Desktop has an upstream Hook-dispatch regression** — some current Codex Desktop / non-interactive CLI builds report Hooks as Active/Trusted without invoking the handler ([openai/codex#21639](https://github.com/openai/codex/issues/21639)). Before any write-capable `md-swarm` / `md-iterate` run in Codex, require both current-session live-probe actions to DENY; otherwise switch to a verified host, currently Claude Code. - *Other minor limitations (citation providers, page locators, escaping edge cases, floating figures, legacy AxMath equations) are not listed here — see the [User Guide](md-技能套件·用户完全手册.md).* ### License diff --git a/md-swarm/SKILL.md b/md-swarm/SKILL.md index bb86f29..a363617 100644 --- a/md-swarm/SKILL.md +++ b/md-swarm/SKILL.md @@ -24,7 +24,7 @@ allowed-tools: [Read, Write, Edit, PowerShell, Bash, Grep, Glob, Agent, AskUserQ > ``` > 然后**按脚本打印的 ACTION 1/2 原样做**:Claude Code 用 Write/Edit + PowerShell/Bash;Codex 用 apply_patch + shell_command。先尝试写临时 `manuscript.md`,再尝试 apply 临时 `机改` changeset;必须亲眼看到两次都被 DENY,再跑脚本打印的 `--check` 和 `--cleanup`。 > -> ⛔ 弱模型适配铁律:**不许只读这个脚本、不许直接调用 hook `.ps1`、不许把 `--check` 的“没落盘”当成已通过**;`--check` 只能发现“真的落盘了”的失败,不能区分 DENY 和你偷懒没做。没看到两个 DENY → 停下:Claude Code 重跑 `verify_hooks.ps1` 并新开会话;Codex 重跑 `setup_all_hooks`、在 `/hooks` 复核 Trust 并重启 Codex;然后重跑 live-probe。 +> ⛔ 弱模型适配铁律:**不许只读这个脚本、不许直接调用 hook `.ps1`、不许把 `--check` 的“没落盘”当成已通过**;`--check` 只能发现“真的落盘了”的失败,不能区分 DENY 和你偷懒没做。没看到两个 DENY → **硬停,不运行任何写入型 md-swarm/md-iterate**。Claude Code 重跑 `verify_hooks.ps1` 并开全新会话;Codex 只在缺安装/缺 Trust 时各补一次。若 `/hooks` 已显示 Active/Trusted 而真实动作仍落盘,**不得循环重装、Trust、重启**:这是 Codex 已知的上游 Hook 调度回归(),切换到已亲眼通过双 DENY 的宿主(当前推荐 Claude Code),待宿主版本或配置真正变化后再复测。 ## Phase 2 总流程(并行起草 + 确定性落盘) diff --git a/md-swarm/preflight.py b/md-swarm/preflight.py index dab6c24..1b8af86 100644 --- a/md-swarm/preflight.py +++ b/md-swarm/preflight.py @@ -200,9 +200,12 @@ def gate(mode='block', context='', allow_no_hooks=False, dry=False, no_heal=Fals if res['ok']: print(' => protection hooks registered.') if not in_claude_code(): - print(' (NOTE: this session is NOT Claude Code -- registered hooks never fire') - print(' here. Layer-1 guards (single-writer apply + citation gates) are the') - print(' active protection. Never write manuscript.md directly.)') + print(' (NOTE: this audit covers Claude registration only; it cannot prove') + print(' Codex/OpenCode adapter dispatch. Require both current-session live') + print(' probe actions to DENY before write-heavy md-swarm/md-iterate. If an') + print(' Active/Trusted Codex adapter still lets either action land, stop; do') + print(' not loop reinstall/Trust/restart. See openai/codex#21639.)') + print(' Layer-1 guards (single-writer apply + citation gates) remain mandatory.') return 0 print(' (NOTE: REGISTRATION only -- not a proof THIS session fires them. A snapshot') print(' taken before the last hook change can still be stale.)') diff --git a/md-swarm/probe_live_hooks.py b/md-swarm/probe_live_hooks.py index 269a68b..8fee686 100644 --- a/md-swarm/probe_live_hooks.py +++ b/md-swarm/probe_live_hooks.py @@ -34,6 +34,7 @@ PROTECT_BAD = "PROTECT_HOOK_PROBE_WRITE_SHOULD_NOT_LAND\n" GATE_BAD = "GATE_HOOK_PROBE_APPLY_SHOULD_NOT_LAND\n" STATE_REL = Path("swarm") / "live_hook_probe_state.json" +CODEX_HOOK_REGRESSION_URL = "https://github.com/openai/codex/issues/21639" def suite_root(): @@ -175,12 +176,25 @@ def check(root): print("") print("RESULT: FAIL -- current-session hook layer is not live.") - print(" Claude Code: run verify_hooks.ps1, then open a brand-new session.") - print(" Codex: rerun setup_all_hooks, review/trust it in /hooks, then restart Codex.") - print(" Rerun this live probe before md-swarm/md-iterate writes.") + for line in failure_guidance(): + print(line) return 1 +def failure_guidance(): + """Return fail-loud remediation shared by runtime output and self-test.""" + return [ + " STOP: do not run md-swarm/md-iterate writes in this harness.", + " Claude Code: run verify_hooks.ps1, then open a brand-new session.", + " Codex: install/review Trust once if missing. If already Active/Trusted,", + " do NOT loop reinstall/Trust/restart; current Codex builds have", + " a known upstream hook-dispatch regression:", + " " + CODEX_HOOK_REGRESSION_URL, + " Switch to a harness where both live actions explicitly DENY.", + " Re-run only after the host/runtime or hook configuration actually changes.", + ] + + def cleanup(root): root = Path(root).resolve() if root.exists(): @@ -205,6 +219,16 @@ def selftest(): if check(root) == 0: print("[FAIL] gate failure should be detected") return 1 + guidance = "\n".join(failure_guidance()) + if CODEX_HOOK_REGRESSION_URL not in guidance: + print("[FAIL] Codex regression URL missing from failure guidance") + return 1 + if "do NOT loop reinstall/Trust/restart" not in guidance: + print("[FAIL] failure guidance can regress into the reinstall/restart loop") + return 1 + if "Switch to a harness where both live actions explicitly DENY" not in guidance: + print("[FAIL] failure guidance does not provide a safe host fallback") + return 1 print("=== selftest: ALL PASSED ===") return 0 finally: diff --git "a/md-\346\212\200\350\203\275\345\245\227\344\273\266\302\267\347\224\250\346\210\267\345\256\214\345\205\250\346\211\213\345\206\214.md" "b/md-\346\212\200\350\203\275\345\245\227\344\273\266\302\267\347\224\250\346\210\267\345\256\214\345\205\250\346\211\213\345\206\214.md" index 42e3a26..2690b3d 100644 --- "a/md-\346\212\200\350\203\275\345\245\227\344\273\266\302\267\347\224\250\346\210\267\345\256\214\345\205\250\346\211\213\345\206\214.md" +++ "b/md-\346\212\200\350\203\275\345\245\227\344\273\266\302\267\347\224\250\346\210\267\345\256\214\345\205\250\346\211\213\345\206\214.md" @@ -5,14 +5,14 @@ > 本手册写给**作者(用户)**:不用懂代码,读完就能独立完成"原稿摄取 → AI 批量改稿 → 出带活 Zotero 域的 Word"全流程。 > 各技能的完整行为定义在其各自的 `SKILL.md`(随技能一起安装)。 > -> **建立**:2026-06-17 · **版本**:v0.11.1 +> **建立**:2026-06-17 · **版本**:v0.11.2 --- > ⚠️ **内测用户须知(用之前先看这三条,能省掉绝大多数"怎么坏了"的麻烦)** > > 1. **只在 Windows + 装了 Microsoft Word 的电脑上用。** 摄取原稿要调 Word(Mac 暂不支持);路径含中文/空格时,让 AI 用 PowerShell 工具跑脚本,**别用 Bash**(会乱码失败)。 -> 2. **第一次用,必须先确认两个"防护钩子"真生效了,再开始改稿。** 这俩钩子是防止 AI 改稿时把你的引用/正文改丢的命脉闸——没装上 = 没保护,而且**不报警**。装法见仓库根目录的 `INSTALL.md`,装完**新开一个 Claude 会话**再跑 `md-swarm\verify_hooks.ps1`,要看到 **6/6 green** 才算数;看不到 = 钩子没生效,**先别改稿**,照该节排查(junction 在不在 / settings.json 注册没 / 重启 Claude Code 没 / `.ps1` 是不是 UTF-8 带 BOM)。**⚠️ "重启 Claude Code" 这步特别容易做错**:在 VS Code 里要**新开一个 Claude 对话**,不是关掉面板再打开(重开若恢复了旧会话、钩子还是没加载);`/clear` 对钩子不可靠、别用它充重启。 +> 2. **第一次用,必须先确认两个"防护钩子"真生效了,再开始改稿。** 这俩钩子是防止 AI 改稿时把你的引用/正文改丢的命脉闸。静态检查通过不等于当前会话真会拦截:Claude Code 先跑 `md-swarm\verify_hooks.ps1`,确认 **6/6 green**,再跑 current-session live-probe,且两个动作都必须明确 **DENY**。Codex 当前存在上游 Hook 调度回归([openai/codex#21639](https://github.com/openai/codex/issues/21639)):即使显示 Active/Trusted,处理器也可能根本没执行。若 Codex live-probe 没有两个 DENY,**立即停止写入型 `md-swarm` / `md-iterate`,不要循环重装、Trust 或重启**;切换到已通过 live-probe 的宿主,当前推荐 Claude Code。 > 3. **这几类稿子现在还处理不了,别拿它们来试,否则会出坏稿:** > - **AxMath 编辑的公式**:会变成 `$$\text{[TODO 重输 LaTeX]}$$` 占位,需你手动补 LaTeX(Word 原生公式不受影响)。 > - **行内公式 / 同行排多个图**:可能在 Word 里显示成字面 `[EQ-2]`。 @@ -241,7 +241,7 @@ powershell -ExecutionPolicy Bypass -File "%USERPROFILE%\.claude\skills\md-build\ 整套是五个标准 Agent Skill(`md-unpack`/`md-triage`/`md-swarm`/`md-iterate`/`md-build`,[agentskills.io](https://agentskills.io) 开放格式),自然语言驱动、可自由组合:大改走 triage + swarm、小修走 iterate、随时 build 出稿。没有 GUI、没有要背的菜单——你只管说要干什么。 -为 **Claude Code** 深度打造("保护钩子"这层物理防线仅 Claude Code 有);同时兼容 **Codex / OpenCode / Hermes Agent** 等支持 Agent Skills 开放标准的工具——OpenCode 原生就会读 `~/.claude/skills`,Codex 用 `~/.codex/skills`,Hermes 用 `~/.hermes/skills`。非 Claude Code 环境没有钩子这层,防护由脚本内置闸门(单写者 apply + 引用不删)+ 仓库根 `AGENTS.md` 守则承担;`preflight.py` 会自动识别非 Claude Code 会话,提示而不拦路。装法见 `INSTALL.md` 第 2 步。 +为 **Claude Code** 深度打造,同时兼容 **Codex / OpenCode / Hermes Agent** 等支持 Agent Skills 开放标准的工具——OpenCode 原生读取 `~/.claude/skills`,Codex 使用 `~/.codex/skills`,Hermes 使用 `~/.hermes/skills`。Codex / OpenCode 的原生适配器只是可选的第二层保护;是否真的生效,只认当前会话 live-probe 的结果。Codex 当前有上游 Hook 调度回归([openai/codex#21639](https://github.com/openai/codex/issues/21639)),Active/Trusted 不是执行证明。未获得两个 DENY 时,不得运行写入型 `md-swarm` / `md-iterate`;应切换到已通过验证的宿主。所有宿主仍必须遵守脚本内置闸门(单写者 apply + 引用不删)和仓库根 `AGENTS.md`。装法见 `INSTALL.md` 第 2 步。 ---