# P0 复盘簿

> 每次 P0 修完在这里记一笔:症状 → 根因 → 为什么会发生(流程层面) → 对应守则。
> 目的不是追责,是让同一类错误只交一次学费。新条目追加在最上面。

## 2026-07-07 · v1.4.1 实例镜像发不出 + 版本耦合把面板锁死(连环)

- **症状**:v1.4.1 发布后,用户升级面板成功,但升级指示器消失、实例卡在 1.4.0(Chromium 150)、浏览器仍黑屏。
- **根因链(两个 bug 叠加)**:
  1. **CI 实例镜像 job 失败**:门禁探针过了,但最终多架构构建在 **QEMU arm64** 下第二个 apt 层 OOM(`Cannot allocate memory`)——两次 `apt-get update` + snapshot 源在后续层被反复拉取,内存压力在模拟环境爆掉。结果 panel job 成功、wechat job 失败 → **只发布了面板、没发布实例镜像**。
  2. **版本耦合无兜底**:面板 1.4.1 因耦合去找 `wechat-on-cloud:1.4.1`(不存在)→ `latestInstanceImageId` 恒 null → 可升级检测全灭;升级也拉不到。一个「防错配」的功能,自己造成了更严重的错配瘫痪。
- **为什么会发生**:
  - 产物门禁只覆盖 amd64(gate build 是 `load:true` 单架构),arm64 只在最终 push 步骤才构建 → 门禁没拦住 arm64 的失败;
  - 版本耦合是「乐观锁」——假定同版本实例镜像一定存在,没考虑 CI 部分失败/发布时序;
  - matrix 两个 job 相互独立,一个失败另一个照发 → 产生「面板新、实例旧」的错配(正是耦合想防的)。
- **修复**:
  1. Dockerfile 合并两个 apt 层为一次 update+install、装完即移除 snapshot 源、`Acquire::Languages=none`+`Retries=3` → 砍掉 QEMU 下的内存/网络峰值。**本机原生 arm64 构建 + 全套探针验证通过**;
  2. `resolveInstanceImage()` 兜底:偏好的版本 tag 在本地和 registry 都不可达 → 回退 `:latest`,保证功能永不因版本错配而瘫痪(已运行时验证)。
- **守则**:R1;门禁原则补充——**门禁必须覆盖所有发布架构**,不能只测 amd64。
- **最终解决(v1.4.4)**:源精简只降概率、不治本——v1.4.3 又栽在 snapshot InRelease 的同类 OOM。确认根因是
  **QEMU 用户态模拟 apt 的内存天花板**(不确定触发),遂**彻底弃用 QEMU**:release.yml 重写为每个 (镜像×架构)
  在**原生 runner**(amd64=ubuntu-latest、arm64=ubuntu-24.04-arm,公开仓库免费)构建、按 digest 推 GHCR,再
  merge job 合成多架构 manifest 打 tag(imagetools 顺带把 blob 复制到 Docker Hub)。教训:① 没直接发版赌运气,
  先 workflow_dispatch 验证流水线,当场逮到「github.repository_owner 含大写 Gloridust → docker ref 非法」——
  actionlint 查不出这类语义错,**只有真跑一遍才发现**;② 修完再验一轮全绿才发 v1.4.4。三次连环构建失败自此闭环。

## 2026-07-07 · v1.4.0 CI 镜像浏览器实例仍黑屏(Chromium 150 漂移)

- **症状**:v1.4.0 发布后,微信恢复正常,但浏览器(Chromium)实例升级后依然黑屏/只见壁纸无窗口,崩溃循环(每 2s 一次,裸 `Trace/breakpoint trap`,无任何错误输出,**没有** DPI 事故的 "not invertible" 特征)。
- **根因**:CI 构建时 `apt` 拉到了 **Chromium 150.0.7871.46**(本地验证用的构建因 apt 层缓存还是 149)。150 在本容器栈上:「非首次启动」路径(`--no-first-run`、存在 `First Run` 哨兵、或同 profile 第二次启动)与 `--start-maximized` **均直接 SIGTRAP**——首跑一次正常、之后必崩,无任何 flag 组合可绕。
- **为什么会发生(两层)**:
  1. **门禁跑错了对象**:发布门禁全项通过——但跑在**本地构建**上;CI 现场重新 `apt` 得到的产物和本地验证的不是同一个东西。**门禁必须跑在即将发布的那个产物上。**
  2. **最高危依赖没有锚定**(R1 没落实到 chromium):Debian 源只保留最新版,每次重建都在抽奖;这已是 chromium 漂移第三次咬人(149 断言收紧、150 非首跑崩溃)。
- **修复**:
  1. Dockerfile 从 `snapshot.debian.org` 固定快照(`20260704T180435Z`)锚定安装 **149.0.7827.196**;升级 chromium = 显式改快照+版本号并过全套门禁;
  2. release.yml 增加**产物门禁**:先构建 amd64 到本地跑四项探针(DPI / CEF 连跑两次含非首跑路径 / 30s 崩溃循环 / 微信模式),全过才推送;
  3. 本地门禁的 CEF 探针也升级为「连跑两次」(单次探针恰好漏掉了 150 的非首跑崩溃)。
- **守则**:R1(锚定);门禁新增原则——**验证过的东西和发布的东西必须是同一个 artifact**。

## 2026-07 · XSETTINGS DPI 单位错误(史上最大,v1.2.9~v1.3.1)

- **症状**:微信点公众号/附件/小程序窗口秒关或白屏(amd64/arm64 都中);Chromium 实例整体黑屏崩溃循环;issue #111 及大量群反馈。
- **根因**:#101 引入的 xsettingsd 配置写 `Xft/DPI 96`,而 XSETTINGS 规范单位是 **DPI×1024**(应为 98304)。所有 Chromium 内核应用把 DPI 读成 0.09 → 缩放因子≈0 → 显示变换矩阵不可逆(`transform.cc NOTREACHED`)→ GPU/viz 进程连崩 → 窗口/应用退出。
- **连带**:崩溃风暴 × docker 日志无上限 × 崩溃转储无 TTL × 悬空镜像不回收 → 用户磁盘被吃(「1TB 没了」);同步升级接口在受限网络下表现为「一键升级卡死」。
- **为什么会发生**:AI 生成 PR 的配置数值未对规范验证(R7);冒烟只测了「微信启动」没测「点开公众号」(R9);第一次修复(v1.3.1 的 `--force-device-scale-factor=1`)用只解释部分症状的理论当根因发布(R6);四个版本挤在 48h 发布,事故窗口内变更太多难以二分(R8)。
- **修复**:`94b591e`(单位改 98304 两处写入点 + 日志上限 + 转储 TTL + 悬空镜像回收 + 升级异步化)。
- **守则**:R3 / R4 / R6 / R7 / R8 / R9。

## 2026-06-25 · 面板自更新后全员 502 黑屏(v1.2.6 修复)

- **症状**:用面板内「一键更新」升级的用户,更新后所有实例远程桌面黑屏、「桌面服务暂不可用」;直接 `docker pull` 的用户无恙。
- **根因**:自更新克隆旧容器配置时把 `Hostname`(= 旧容器短 id)一并克隆;新面板容器的 hostname 于是指向一个已删除的容器 → `ensureNetwork()` 用 `getContainer(hostname())` 自我定位 404 → 返回 null → 新建实例落到默认 bridge 网络 → 面板按容器名反代解析不到 → 502。
- **为什么会发生**:自身状态靠「克隆运行时环境」而非声明式重建;自我定位单点依赖 hostname 无兜底。
- **修复**:`7e21aca`(重建不再 pin Hostname;ensureNetwork 增加按容器名 `woc-panel` 兜底)。
- **守则**:R2。

## 2026-06~07 · 桌面连接/重连卡死连环修(4 连击)

- **症状**:切实例 A→B→A 卡死、重连后 Xvnc 卡死、输入模式切换不生效等,先后修了 4 次(`13ec0bd`→`855997f`→`7fecc09`→`b0511f5`)。
- **根因**:noVNC/iframe 的连接生命周期管理散落多处,每次只修当时复现的那条路径。
- **为什么会发生**:补丁式响应,没有对「连接状态机」做一次性的不变式设计;每个补丁都没解释之前补丁为何不够(R6 的弱版本)。
- **守则**:R6;同类问题第三次出现时必须停下来做整体设计,而不是继续打第四个补丁。

## 2026-06 · 深色模式 portal 方案发布后撤回

- **症状**:portal 实时切换深色在极简容器里 session 总线不稳,微信也不跟随;发布(`335b839`)后撤回(`ba32cc9`)。
- **为什么会发生**:方案依赖容器里并不可靠的桌面基础设施,发布前未在目标环境充分验证。
- **守则**:R9(验证用户动作,而非「代码跑通」)。
