---
title: 管道吞退出码根治 + 批修自伤（含 d6cddb2 的 8 处破坏）
tags: [静默失败, 父任务假成功, 批修, 铁律8]
created: 2026-09-12
status: final
---

# 管道吞退出码根治 + 批修自伤

## 一、父任务假成功⑮: `CMD | head/tail || FALLBACK`

管道退出码 = `head/tail` 的 = 0 → `||` 永不触发 → 失败被静默。
全库 46 处 / 22 文件（其中 3 处 `ls|head||echo "无"` 合法），**害的 34 处 / 16 文件**已修。

**修法（行内改写，行数不变）**:
```bash
CMD | head -N || FALLBACK
→  _PIPE=$( CMD ); _rc=$?; echo "$_PIPE" | head -N; [ "$_rc" -eq 0 ] || { FALLBACK'; FAILED=1; }
```
+ 每个文件末尾补 `[ "${FAILED:-0}" = 1 ] && exit 1` 传导。

**注意**: 原 fallback 里的 `$?` 在新写法中已不是 payload 的 rc（而是方括号判断的）→ 必须改用 `$_rc`（修了 6 处）。
`|| true`（作者故意容忍）的行**不加 FAILED**，只改可见提示（尊重原意）。

## 二、批修自伤: d6cddb2 的 8 处破坏（12 文件受害）

今天 06:11 的另一次批修把 fallback **插进了 `python3 -c "` 的跨行字符串**：
```
timeout 60 python3 -c " 2>/dev/null || bash ".../on_soft_fail.sh" "x.sh" $?   ← 插在开引号后
from cron_context_chain import save_1000_pattern
" 2>/dev/null || true
```
⇒ `python3 -c` 收到 ` 2>/dev/null || bash ` → SyntaxError；后续行被 shell 当命令跑。
**`bash -n` 通过**（引号配平后语法合法）。**「写入 cron 上下文链」步骤静默失效 7 小时**。

**检测判据**: 含 on_fail 调用 且 **整行未转义双引号数为奇数**。
**FP 实测**: v1 判据 208 处假阳性（路径开引号被误判）→ v2 判据 14 处真实命中 → 修 8 处 ✅。

**给批修器的四条硬门**（本轮两次自伤后定稿）:
```
① 正则不跨行（[^\n]* / (?m) 锚点，不用 .*$）
② 行数不变断言（改动只应在行内）
③ 语法检查（bash -n / py_compile）再 + 语义检查（引号奇偶 / 抽出内嵌代码）
④ 逐行 diff 审阅
```

## 三、给"验证"本身立的规矩

失败支路注入实测时，**成功路径回归不能跑真脚本** —— 实测触发了 `analysis_pipeline.py --daily`
改写了 `pool/pools.json`+`data/stock_pool.json`（已 `git checkout` 恢复）。
⇒ 用**合成样本**验证逻辑，或 stub 掉写操作。**验证成本 ≠ 可以污染数据。**


---

## 四、全系统扩扫结论（2026-09-12 第二轮）

前一轮只扫了 `~/.hermes/scripts/*.sh`；本轮扩到 `~/.hermes/{scripts,bin,hooks,plugins}` + `~/stock_workspace`。

### 类5（`timeout N <shell内建>`）: 其它目录 **0 处真**
首次扫描报 14 处 —— **全是我的假阳性**：判据里的 `\.` 匹配到了 `.venv/bin/python3`
（`. ` 是 source 的别名，但 `.venv` 不是）。修正为「内建后必须紧跟空白/行尾」后归零。
⇒ **类5 全库确认清零**（scripts 内 14 处真早在第一轮已修）。

### 类6（`CMD || echo "<假成功文案>"`）: 其它目录 **31 处**
`~/.hermes/bin` 2 / `~/stock_workspace` 2 / 其余在 scripts。
剔除合法惯用法后修 **27 处**（含 `a_group_data.sh` 早前被 `SKIP` 集合漏掉的 3 处），
另修 `~/.hermes/bin/vector_memory.sh`（**scripts 版的陈旧副本**，两份内容已漂移）
与 `~/stock_workspace/midday_cron_wrapper.sh`（与 scripts 版**已分叉**）。

**管道左端是 payload 时**（`payload | python3 -c`）不能只捕获一端 rc → 用**双 rc 检查**：
```bash
_PIPE=$( payload ); _rc=$?; echo "$_PIPE" | REST; _rc2=$?; \
  { [ "$_rc" -eq 0 ] && [ "$_rc2" -eq 0 ]; } || { echo "失败(payload_rc=$_rc pipe_rc=$_rc2)"; FAILED=1; }
```
左端是 `echo`/`cat`/`printf` 时**不能**捕获左端（左端恒 0 → 反把右端失败吞掉）→ 直接用普通 `||` 块 ✓。

### 合法 `|| true` 白名单（22 处不动，写进门禁）
`grep -c/-q ... || true`（rc=1=无匹配，正常）/ `cp|mkdir|rm|find|touch|source ... || true`
/ `curl ... >/dev/null 2>&1 || true`（尽力而为的通知）。
**门禁原来一刀切**，把上面这些全报成类6 → 已加窄白名单 + `# ACCEPTED: <理由>` 标注机制，
并用**反例自证**验证：改前 6 处命中（2 真 + 4 合法）→ 改后**精确只剩 2 处真病灶**。

### 🔴 自己造的第三个事故: 守卫落进共享库
补 `FAILED` 传导守卫时，机械脚本把 `[ "${FAILED:-0}" = 1 ] && exit 1` **追加到了 `pre_router.sh`** ——
它是被 **106 个脚本 `source`** 的共享库。sourced 文件的最末语句 rc 会成为 `source` 的返回值
⇒ 任何 `set -e` 的调用方会被连带中断。
**已 `git checkout` 回滚**，并补了通用检查「改动前先问：这个文件被 source 吗？」
（本次另外 2 个疑似 source 命中经查是**文件名子串假阳性**与**未改动的上报器**。）

### 检测器 v3 校准（curator 生成的脚本，我改了判据）
`scan_midstring_injection.py` 的 v2 判据（整行引号奇偶）对**已修好的收尾行**系统假阳性
（14 处命中全是修好的行）→ 改**精确正则**「同行同时出现 `python3 -c "` 与 fallback」。
selftest 3/3（坏样本命中 / 好样本不误报 / 收尾行定位）→ 实扫 0 命中 ✅。已登记 A14（FP 留 null + 理由）。


---

## 五、批修自伤的**三类形状**（2026-09-12 收口）

批修脚本「追加 fallback」的动作，插错位置会造出三种静默破坏。检测器
`~/.hermes/scripts/midstring_injection_guard.py`（v5）现在**三类都扫**：

| # | 形状 | 实证样本 | 为什么 `bash -n` 抓不到 |
|---|---|---|---|
| ① | 插进 `python3 -c "` 的**跨行字符串** | `timeout 60 python3 -c " 2>/dev/null \|\| bash "…on_soft_fail.sh" …` | 引号配平后语法合法 |
| ② | 插到**反斜杠续行**之后 | `… \| bash "…upload…" \ 2>/dev/null \|\| bash "…on_fail.sh" …` → 下行的标题参数变成独立命令 | 续行仍是合法的两行命令，只是语义断了 |
| ③ | 插到**语法位置**之后（`; then` / `; do` / `else`） | `timeout 60 if python3 -c "…" 2>/dev/null; then 2>/dev/null \|\| bash "…on_soft_fail.sh" …` | **这个能报**，但报错点(`unexpected token then`) 需要人读到才发现是批修造成 |

### 关键纠正: ③ 是**长期存量**，不是"无关的既有问题"

我上一轮把 `auto_scan_wrapper.sh` / `preflight_check.sh` 的语法错误判为
「HEAD 版就有 → **独立既有问题，非我引入**」→ 不准确。
查 git 首提交（`init: cron脚本层基线`）发现**同样的错位从第一版就在**：

```bash
timeout 60 if python3 -c "…" 2>/dev/null; then 2>/dev/null || echo "auto_scan_wrapper.sh: timeout"   ← init 版
timeout 60 if python3 -c "…" 2>/dev/null; then 2>/dev/null || bash "…on_soft_fail.sh" "x" $?          ← 今天批量替换后
```

也就是说：**今天的批修只是把旧的错误文案换成新文案，位置错误一动不动** ——
属于「同一 bug 只换了个说法」，而两次都被判为「不是我改的」。
正确结论：**它是批修家族的判定/修法都漏掉的第三类**，必须修而不能留。

修法：`timeout N if …` → `if timeout N …`（`timeout` 不能包 `if` 关键字），
fallback 直接**删除**（`else` 分支本就负责报告）—— 例：
```bash
if timeout 60 python3 -c "from pipelines.auto_scan_pipeline import run" 2>/dev/null; then
```
修后全库 `bash -n` **语法错误 0 个**（原 2 个）。

### 附带修的 2 处管道吞 rc
- `fugui_daily_review_wrapper.sh` — `python3 … \| grep -v …`：`grep -v` 全过滤时 rc=1 / 有一行存活则 0
  → 双重语义，**不能用管道 rc 判断 payload 成败** → 捕获左端 rc
- `system_audit_wrapper.sh` — 除管道吞 rc 外，grep 模式里被插过 ` timeout 60 `
  （`grep -v "WestockDriver\| timeout 60 未安装"`）→ **连过滤条件都被污染**，已还原为 `"WestockDriver\|未安装"`
