---
date: 2026-05-08
type: error
tags: [ops, env, multi-env, audit, ffai, aixc, jwt]
---

# 多环境 env 同步漏一个：AIxC-UAT 漏改 JWT env，最终检查才捕到

## 现象

工单 #242 的 4 套环境（FFAI UAT/PROD + AIxC UAT/PROD）都需要把 JWT env 名从历史 `JWT_EXPIRES_IN` 改为 `JWT_ACCESS_TTL` + 值改 30d。

最终一致性检查时实测每台 `/api/v1/auth/login` 的 `expiresIn` 字段，发现 AIxC-UAT = **1800（30 分钟）**，而其他三台都是 2592000（30d）。AIxC-UAT 被漏改了。

## 根因

操作分布在多个时间点，前任操作的范围我接手时没准确盘点：

| 环境 | 改 env 时机 | 改 env 操作者 |
|---|---|---|
| FFAI-UAT | 上次会话 (2026-05-07 21:55) | 用户手动 sed |
| FFAI-PROD | 本次会话 | AI（用户授权后）|
| AIxC-PROD | 本次会话 | AI（用户授权后）|
| **AIxC-UAT** | **漏了** | **没人改** |

我接手时记忆里默认了"4 套 UAT 都改过"，没有逐台核对。直到最终一致性检查才发现 AIxC-UAT 仍是 `JWT_EXPIRES_IN=7d`（旧名+旧值），代码读不到 → 兜底 30min。

## 关键发现

**实测 `/api/v1/auth/login` 拿到 `expiresIn` 字段，是验证 env 是否被代码读到的最有效手段**。比 grep .env 文件可靠：

- grep `.env` 只验证"键名对没对"，不验证"代码是不是真读到了"
- 实测登录响应反映"代码 + env + reload" 三者都对的状态

如果只 grep env 文件，AIxC-UAT 的旧名 `JWT_EXPIRES_IN=7d` 看起来"还有 JWT 配置"——但完全没生效。这是隐性失败。

## 预防：多环境批量操作的"显式表格"模式

下次任何"多套环境同时配置变更"任务，强制走这个流程：

```
| 环境 | 当前状态 (实测) | 期望状态 | 已操作 | 已验证 |
|---|---|---|---|---|
| FFAI-UAT | ... | ... | ⏳ | ⏳ |
| FFAI-PROD | ... | ... | ⏳ | ⏳ |
| AIxC-UAT | ... | ... | ⏳ | ⏳ |
| AIxC-PROD | ... | ... | ⏳ | ⏳ |
```

**每行都跑实测，不靠记忆 / 不靠假设**。即便有"前任已经做过"的传言，AI 接手也要重新验证。

## 验证脚本（一键全检）

```bash
for host in <list>; do
  IFS=":" read -r TARGET LABEL ENVF PORT <<< "$host"
  ssh "$TARGET" "
    grep -E '^JWT' $ENVF | sed 's/=.*/=<value>/'
    grep -E '^(JWT_EXPIRATION|JWT_EXPIRES_IN|JWT_REFRESH_EXPIRES_IN)=' $ENVF || echo 'no-legacy-key'
    curl -sS -X POST http://localhost:$PORT/api/v1/auth/login -H 'Content-Type: application/json' -d '{...}' | python3 -c '...'
  "
done
```

跑一次就把"键名 + 旧名残留 + 实测 expiresIn"三件事一起看到，比单查任何一项可靠。

## 适用范围

- 任何 4 台机器分布的项目（FFAI / AIxC × UAT / PROD）
- 任何"env rename"或"env value change"这类场景
- 任何 AI 接手部分操作过的任务（"前任做了一半"是隐性盲区）

## 关联

- 工单 #242 / `ERR-20260508-002`（AIxC ESM 隐患）
- `ERR-20260508-001`（nvm 升级 4 连环坑）
- `2026-05-08-node-version-drift-across-envs`（环境治理）
