# 在错的机器上测出来的绿 —— 交付给人跑的脚本，两版都「我这儿全过、他一跑就炸」

> 2026-08-27。触发场景 = 主机 `GIT_AUTH` 轮换（STATUS §一 3 ②）。签发/吊销必须 owner 在他自己的终端做，
> 于是我要交付两份 bash 脚本给他粘贴运行。**两版都在他机器上当场报错，而两版我都「测过」。**
> 本篇只记那个可复用的机制，轮换本身的实录在 [STATUS-CHANGELOG](../STATUS-CHANGELOG.md)。

---

## 1. 事实

| 版 | owner 终端的报错 | 病因 | 我为什么没测出来 |
|---|---|---|---|
| v1 | `HDR[@]: unbound variable` | `set -u` 下展开**空数组** | **macOS 自带 `/bin/bash` 是 3.2.57**（bash 4.4 才修这个）。我用 `/bin/bash` 测了 —— 但没测到这条路径，因为我给的输入让 `HDR` 非空 |
| v2 | `ID?: unbound variable` | `$VAR` **紧跟多字节字符**时，bash 3.2 把该字符的首字节吃进变量名（`$ID（` → `ID\xef`） | 🔴 **只在 UTF-8 locale 下发作，而 Claude Code 的 Bash 工具里 `LANG` 是空的（C locale）**。同一行我跑四个变体全过 |

v2 的复现是决定性的：

```
LANG=zh_CN.UTF-8  → /bin/bash: ID?: unbound variable
LANG=en_US.UTF-8  → /bin/bash: ID?: unbound variable
LANG=C            → 没报错
```

⇒ **变量不在脚本里，在环境里。** 我和 owner 跑的是同一份文件、同一个 bash 二进制，
只有 `LANG` 不同，结果一红一绿。

## 2. 这是「取证环境造假结论」的第几个形态

本仓已登记过三例，都是**取证工具/环境**制造出不存在的现象：

| 例 | 造假的那一层 | 方向 |
|---|---|---|
| `python3 -m http.server` 不送 charset ⇒ 中文乱码 ⇒ 量出不存在的溢出 | **响应头** | 造出假缺陷 |
| 软链 `dist` 进 worktree ⇒ 闸比的是别人的半成品产物 | **文件系统布局** | 造出假缺陷 |
| worktree 缺 `.env` / `figma-data/mockup/` ⇒ 闸走 fail-open 分支 | **输入数据在不在** | 造出假通过 |
| **本例：`LANG`** | **环境变量** | **造出假通过** |

前三例改的都是「被测对象周围的东西」。本例更靠里：**改的是解释器本身的行为**。
⇒ 判据不能只问「输入面对不对」，还要问「**解释这段代码的那个东西，在他那边是不是同一种行为**」。

## 3. 两条可执行的收敛

### (a) 交付「给人在自己终端跑」的脚本，测试必须显式带真实 locale

```sh
LANG=zh_CN.UTF-8 LC_ALL=zh_CN.UTF-8 /bin/bash script.sh
```

两个都不能省：`/bin/bash`（不是 `bash` —— 后者可能是 Homebrew 的 5.x，测不到 3.2 的坑）+
真 locale。**只在默认环境跑一遍 = 假绿。**

### (b) 修的时候按**形态**全扫，别只补出事那一行

v2 出事的是第 55 行。按形态（正则找「`$VAR` 后接非 ASCII 字节」）扫完两个脚本：

| 文件 | 命中 | 备注 |
|---|---|---|
| `gitea-revoke.sh` | **7 处** | 出事的只是其中 1 处 |
| `gitea-mint.sh` | **3 处** | **全在没走到的错误分支里** |

🔴 `gitea-mint.sh` 那 3 处最值得记：那份脚本 owner **成功跑完过**，产出了 token。
⇒ **「这个脚本跑成功过」不能证明它没有同类缺陷** —— 它只证明那天没走到那些分支。
同族纪律：本仓「回归测 PASS 需故障存在性证据」的镜像面。

## 4. 一条别读错的

两次都**恰好** fail-closed：v1 死在 `curl` 之前、v2 死在 `DELETE` 之前，**什么都没删**。

⛔ **那是运气，不是设计。** v2 那次脚本已经走完「列出 4 条 token → 唯一命中 id=9」，
只差一行确认就执行不可逆的吊销。判据换个位置就是另一个结局。
⇒ 别把「这两次没出事」当成脚本安全的凭据；真正起作用的防线是别的：
**按后八位程序化精确匹配 + 没有唯一命中就拒绝动手 + 删除前必须输 `yes`**。

## 5. 顺带订正我自己的一句话

v1 事故后我对 owner 说「认证没过」—— **那是脚本误报，curl 根本没跑起来**（死在数组展开）。
脚本把「取不到 login」一律当成认证失败，没把 **curl 的退出码**与 **HTTP 状态码**分开取。
v2 已改成两者各自有各自的话（`curl 非 0 = 连都没连上，这不是认证失败`）。
⇒ 又一次「判据必须取终态事实」：**「没得到期望结果」≠「得到了失败结果」。**

## 6. 落点

- 通则已升 memory `reference_local-env-tool-quirks`（含索引行）
- 轮换本身的逐步实录 + 三条主机机制 → [STATUS-CHANGELOG](../STATUS-CHANGELOG.md) 顶条 + [`infra/README.md`](../../../infra/README.md) §主机侧回滚源
