# cron 脚本「裸 python3」陷阱 — 事故记录与修复（2026-10-01）

> 本文对应技能 `baoyu-workbench-health-check`（属 default profile，zone-b 不可改），
> 故按「跨 profile 不可改 → 落本地知识文件」铁律记于此。
> 体检时应把本文当该技能的第十一节看待。

## 一、症状

cron 日志里反复出现：

```
ModuleNotFoundError: No module named 'pandas'
  File ".../脚本/erp_sales_core.py", line 12, in <module>
    import pandas as pd
```

**但任务状态仍报 `ok`** —— 这是最危险的地方。

## 二、根因链（已逐环取证）

| 时间 | 事件 |
|---|---|
| 2026-09-27 03:30 | `hermes update` 自动升级 `v0.21.3 → v0.21.5+2820.g661a22a`；重启 gateway；zone-b 被判 **STALE**、脚本 exit 1（MARK 未推进，会每两周重试） |
| 2026-09-28 08:19 | zone-b gateway 重启，**PATH 首位变成** `~/.hermes/installs/<hash>/environments/<hash>/venv/bin` |
| 2026-09-29 起 | cron 里所有 `import pandas` 的步骤全崩，**连续 3 天**（09-29 / 09-30 / 10-01） |

那条 installs venv 里的 `python3` 是 **Python 3.14.7，没有 pandas**。
PATH 里靠后的才有：

| 解释器 | 版本 | pandas |
|---|---|---|
| `~/.hermes/installs/<hash>/environments/<hash>/venv/bin/python3` | 3.14.7 | ❌ **无** |
| `~/.hermes/tools/python-3.14.7+…/bin/python3` | 3.14.7 | ❌ 无 |
| `/usr/bin/python3` | 3.9.6 | ✅ 2.3.3 |
| `/opt/homebrew/bin/python3` | 3.14.3 | ✅ 3.0.2 |
| `~/.hermes/hermes-agent/venv/bin/python3` | 3.11.15 | ✅ 3.0.5 |

`cron(no_agent)` 脚本由 gateway 子进程执行 → 继承其 PATH → 裸 `python3` 命中 3.14.7。

## 三、为什么「崩了却报 ok」

两个叠加因素：

1. **每步都带兜底**：`python3 xxx.py >> "$LOG" 2>&1 || echo "⚠️ 失败" >> "$LOG"`
   → 失败被吞进日志，退出码仍是 0。
2. **部署步骤照常成功**：`tcb` 在 PATH 里（`~/.npm-global/bin`），
   → 把**上一轮的旧文件**重新推上线，线上看不出异常。

结论：**整条定时管道对所有 ERP 派生物静默空转**。

## 四、波及面（当时实测）

`cb_sync.sh` 一次运行（cron 触发）里 8 个脚本全崩：

```
❌ mcp_kpi_pipeline.py      （无 pandas）
❌ refresh_erp.py           （无 pandas）
❌ refresh_erp_amount.py    （无 pandas）
❌ refresh_key_customers.py （无 pandas）
❌ build_price_index.py     / build_order_data.py / gen_orders_detail.py / build_product_mapping.py
```

`daily_full_refresh.py` 的 `KPI更新` 同样崩（09-29 ~ 10-01 连续 3 天）。

## 五、修复（已落地并验证）

### 1. `脚本/cb_sync.sh`
- 顶部 `unset PYTHONPATH PYTHONHOME VIRTUAL_ENV`（3.14 venv 的 PYTHONPATH 会污染 3.9 的 C 扩展加载）
- 探测并**钉死** `PY`（依次 `/usr/bin/python3` → `/opt/homebrew/bin/python3` → `/usr/local/bin/python3` → PATH）
- 全部 10 处调用由裸 `python3` 改为 `"$PY"`
- 新增 `_step()` 包装 + `FAIL` 计数器；收尾**失败即 `exit 1`**（不再静默）
- `merge_workbench_data.json` 挪到部署 FILES **之前**（同一次运行内先合并再上线）
- 探测不到 pandas 解释器 → 硬退出 1

### 2. `~/.hermes/scripts/daily_full_refresh.py`
- 新增 `_clean_env()`（剥 PYTHONPATH/PYTHONHOME/VIRTUAL_ENV）
- 新增 `_resolve_python()`；`run_step` 改用 `PY` 且传 `env=_clean_env()`
- `main()` 开头硬校验，探不到 → `sys.exit(2)`；启动打印所选解释器

### 3. 备份

```
脚本/cb_sync.sh.bak-pypin-20261001-221536
~/.hermes/scripts/daily_full_refresh.py.bak-pypin-20261001-221536
```

## 六、验收口径（双轴，缺一不可）

**轴 1 — 在 gateway 的真实 PATH 下跑完整管道**

```bash
GWP=$(ps eww <gateway_pid> | tr ' ' '\n' | grep -E "^PATH=" | head -1 | cut -d= -f2-)
env PATH="$GWP" HOME="$HOME" python3 ~/.hermes/scripts/daily_full_refresh.py   # 裸 python3 起，故意复现
```
实测：`解释器: /usr/bin/python3` → **7/7 全绿、exit=0**。
`cb_sync.sh` 同法：`exit=0`，全部步骤 ✅。

**轴 2 — 本地 sha 与 CDN sha 逐文件比对**

7 个派生物 **7/7 一致**；CDN 上 `kpi_progress.json` 实测
`month=2026-09 / total=431.4万 / file=销售订单_20260101-20260930.xlsx`。

只做轴 1 会漏掉「跑通了但没传上去」；只做轴 2 会漏掉「传了但是旧文件」。

## 七、方法论要点

1. **体检时先取 gateway 的 PATH 再下结论**：`ps eww <pid>` 才能看到 cron 的真实环境，
   在交互式 shell 里 `which python3` 是**假阳性**（交互 shell 的 PATH 首个是 `/usr/bin`）。
2. **`no_agent` cron 报 `ok` 不等于跑成功**：只要脚本有 `|| echo` 兜底就不会非零退出。
   看到 `ok` 也要抽查产物 mtime / CDN sha。
3. **编排脚本一律用绝对解释器**，不要依赖 PATH。
4. **失败要吵**：静默降级比崩溃更贵——崩溃当天就被发现，静默能烂一周。

## 八、遗留未处理（另行拍板）

- `a9bc41c50009`「Hermes 每两周自动更新」：`last_status=error`（fleet 判定 zone-b STALE），
  且 `deliver=origin` 落到 **weixin（zone-b 未配置）** → 投递必失败。下次运行 2026-10-04 03:30。
- zone-b gateway launchd plist 相对当前 Hermes 安装 **已 stale**（`hermes gateway start` 可修，
  但**禁止在 gateway 进程内重启自己**，需用户在新终端执行）。
