docs(bug-history): record the image-build defect fixed in this batch and one open engine mismatch
BUG-257 documents the skills/ COPY collision fixed in the previous commit, as the workflow requires a fix to land together with its record. It notes the missing automated regression instead of hiding it: reproducing the defect needs a real image build, so the entry states the gap and proposes a structural guard. BUG-258 records that node:22-alpine resolves to Node v22.15.0 while posthog-node@5.41.0 declares ^20.20.0 || >=22.22.0. Filed as investigating, not resolved: the version range genuinely does not match and it predates the Next upgrade, but whether the package actually calls Node 22.22 APIs is unconfirmed, and both remedies are out of scope for this batch. Renumbered the React Compiler record from BUG-255 to BUG-256 while rebasing: origin/staging had taken 254 and 255, and its 255 is an unrelated bug, so BUG-254's rule applies and a distinct number was assigned rather than merging. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -3846,3 +3846,35 @@
|
||||
- 防复发:**「配置开启 + 构建绿」绝不等于「React Compiler 生效」**。编译器放弃某个组件时不报错、不警告、不留日志,这是它的默认行为(`panicThreshold: "none"`)而非缺陷。任何启用 React Compiler 的改动都必须在构建产物里定位目标组件、确认存在编译器注入的缓存槽,并用 `"use no memo"` 做一次反向验证证明取证方法会随编译状态变化;只贴构建退出码等于没验证。另外 `"use memo"` / `"use no memo"` 是函数体内的指令,写在文件顶部时 `"use no memo"` 可作整文件退出、但 `"use memo"` 不构成整文件加入。
|
||||
- 相关记录:BUG-260 无前序同类记录。本记录三次改号:初次写作取 255;第一次 rebase 到 `origin/staging`(`c8d9ec64`)时远端已占 254–255,改 256;第二次 rebase 到 `e1db5762` 时远端又占到 259,改 260。每次都按 BUG-254 的防复发要求处理——标题与远端各记录均不同,故另分新号而非合并。BUG-253 所记的抢号失效模式在同一轮交付里连续复现两次,暴露出「追加前检索最大编号」这条措施的边界:它只在写记录那一刻成立,防不住推送前远端继续前进。可靠做法是把定号推迟到推送前最后一次 rebase 之后。
|
||||
- 修复版本:本地未提交候选
|
||||
|
||||
## BUG-257 | staging web 镜像把 `skills/` 拷两遍,符号链接撞上被解引用的同名目录
|
||||
|
||||
- 状态:resolved
|
||||
- 首次发现:2026-08-17
|
||||
- 最近更新:2026-08-17
|
||||
- 影响面:`deploy/railway-web.Dockerfile` 的镜像构建,即 staging 与生产的 web 镜像产出。构建期缺陷,不影响已在运行的实例。
|
||||
- 用户现象:无终端用户可见现象。对维护者而言:`docker build -f deploy/railway-web.Dockerfile` 在最后一步失败,`ERROR: failed to build: failed to solve: cannot replace to directory /var/lib/docker/buildkit/containerd-overlayfs/cachemounts/<id>/app/skills/jyotish-vedic-astrology/assets with file`,指向 `COPY --from=build /app/skills /app/skills`。危险之处在于它**依赖 BuildKit 实现才会暴露**:Gitea/GitHub runner 上一直构建成功,本地 Docker 29.1.3(containerd-overlayfs 快照器)必定失败,因此这是一颗按 runner 环境触发的定时炸弹,而不是一个稳定可见的错误。
|
||||
- 触发条件:用一个不允许「以文件覆盖已存在目录」的 BuildKit 快照器构建该 Dockerfile。与 Next 版本、CPU 架构均无关:在升级前的基线提交(`next` 16.2.10)与 amd64/arm64 两种平台上都能复现同一条错误。
|
||||
- 根因:同一份 `skills/` 以两种互不兼容的形态进了最终阶段。`skills/jyotish-vedic-astrology/assets` 是仓库里 git 跟踪的符号链接(`-> ../../assets`)。`frontend/src/lib/skill-package-registry.ts` 存在动态文件访问,Next 构建期打印 `Dynamic filesystem access ... causes tracing of the whole project`,于是文件追踪把 `skills/` 整棵树带进 `.next/standalone/`,并在拷贝时把符号链接**解引用成真目录**。最终阶段第一步 `COPY --from=build /app/frontend/.next/standalone /app` 先把这份解引用产物落到 `/app/skills`,随后第 36 行 `COPY --from=build /app/skills /app/skills` 又要把源码树里的符号链接放到同一路径上——用文件覆盖目录。两行 COPY 相隔 5 行、意图并不冲突,冲突完全由中间那层不可见的文件追踪行为造成。
|
||||
- 修复:在构建阶段 `npm run build` 之后加一行 `RUN rm -rf /app/frontend/.next/standalone/skills`,删掉追踪产物里的那份,让显式 `COPY skills` 确定性地独占该路径。没有改动两行 COPY 的顺序或语义,也没有改 `outputFileTracingIncludes`:`/app/assets` 仍由 standalone 提供,而它是 `outputFileTracingIncludes` 为 `/api/consult` 显式声明的(`../assets/**/*`),不是追踪的附带产物,因此符号链接照旧可解析。
|
||||
- 验证:修复后完整构建镜像成功(`naming to docker.io/library/jyotisha-web:fixed done`)。进镜像逐项核对:`/app/skills` 下 1208 个文件、`assets` 仍是 `lrwxrwxrwx ... -> ../../assets` 且 `ls` 能列出其中文件、`/app/frontend/server.js` 存在。运行时冒烟:容器启动打印 `▲ Next.js 16.3.1`,`GET /` 返回 200(日志中的 `SupabaseConfigurationError` 是未注入环境变量所致,属预期)。对照实验确认与本批 Next 升级无关——在升级前的 `3371baca`(`next` 16.2.10)上用同一条命令构建,在同一步报同一条错误。
|
||||
- 待跟进:**本条没有自动化回归测试**,与 BUG_HISTORY 工作流对 `resolved` 的要求存在缺口,此处如实标注而非掩盖。原因是复现该缺陷必须真的构建镜像(本机约 16 分钟),放不进 `npm test`。可行的折中是在 `tests/health-deployment.test.ts` 加一条结构断言:若 Dockerfile 同时存在「拷贝 standalone 到 `/app`」与「显式拷贝 `skills`」两行,则必须存在删除追踪副本的一行。这是结构守卫而非行为回归,能防住这一行被顺手删掉,未在本轮加入。更彻底的方向是消除 `skill-package-registry.ts` 的动态文件访问以恢复精确追踪,那属业务代码改动,另开一轮。
|
||||
- 防复发:Next 的输出文件追踪会把项目文件带进 `.next/standalone/` 并**把符号链接解引用成真目录**;任何 Dockerfile 若既拷 standalone 又显式拷同名目录,两者就在争同一路径,必须显式指定谁赢,不能靠 COPY 顺序碰运气。仓库内 git 跟踪的符号链接(如 `skills/*/assets`、`skills/*/references`、`skills/*/scripts`)是这类冲突的固定诱因。更普遍的教训:镜像构建在 CI 上绿不等于 Dockerfile 正确,凡涉及符号链接与目录同名的 COPY,判定必须落到「换一个 BuildKit 快照器是否仍然成立」。
|
||||
- 相关记录:BUG-257 无前序同类记录。与 BUG-256 同批交付但成因无关,是为验证 BUG-256 的 Next 16.3.1 升级而首次在本地构建 staging 镜像时暴露出来的。
|
||||
- 修复版本:本地未提交候选
|
||||
|
||||
## BUG-258 | staging 镜像内 `posthog-node` 的 Node 引擎要求高于基础镜像实际版本
|
||||
|
||||
- 状态:investigating
|
||||
- 首次发现:2026-08-17
|
||||
- 最近更新:2026-08-17
|
||||
- 影响面:staging 与生产 web 镜像内的 `posthog-node@5.41.0`(产品分析上报)。
|
||||
- 用户现象:目前无任何可见现象。仅在镜像构建日志里表现为一条 npm 警告:`npm warn EBADENGINE package: 'posthog-node@5.41.0', required: { node: '^20.20.0 || >=22.22.0' }, current: { node: 'v22.15.0', npm: '10.9.2' }`。
|
||||
- 触发条件:构建 `deploy/railway-web.Dockerfile`。`node:22-alpine` 当前解析到 Node v22.15.0,而 `posthog-node@5.41.0` 声明需要 `^20.20.0 || >=22.22.0`,v22.15.0 落在两个区间之外。
|
||||
- 根因:尚未确认实际影响面。已确认的事实只有两点:一是版本区间确实不满足,二是与本批 Next 16.3.1 升级无关——`git diff 3371baca 37938187 -- frontend/package-lock.json` 中没有任何 `posthog` 相关改动,且该警告在升级前的基线构建日志里就已出现。尚未确认的是 `posthog-node` 是否真的用到了 Node 22.22 才有的 API:`EBADENGINE` 只是警告、不阻断安装,npm 不会因此拒绝装包,所以风险形态是「构建期无声、运行期在某个具体调用上抛错」,而不是构建失败。
|
||||
- 修复:未修复。两条候选路径的代价都超出本轮范围:升基础镜像到 `node:24-alpine` 会引入一整片新的验证面(原生模块需重编、`sharp` 的 musl 二进制需重新确认);降 `posthog-node` 需确认低版本是否仍满足现有调用。
|
||||
- 验证:不适用(未修复)。已核实的事实见根因。
|
||||
- 待跟进:先确定这是真风险还是纯噪声——读 `posthog-node@5.41.0` 的 changelog/引擎声明来源,确认它把下限提到 `>=22.22.0` 是因为用了新 API 还是仅仅是维护者的支持策略。若属后者,本条可降级为「已知噪声」并就地记录,不必改依赖。
|
||||
- 防复发:镜像构建日志里的 `EBADENGINE` 不得当作噪声跳过——它意味着依赖声明的运行环境与镜像实际提供的不一致,而 npm 不会阻止这种安装,因此它是少数「构建期唯一一次提示、之后只会在运行期爆发」的信号。基础镜像用浮动 tag(`node:22-alpine`)时,实际 Node 版本会随上游重建漂移,依赖的引擎下限也会随升级上移,两者相向移动,这类不匹配会反复出现。
|
||||
- 相关记录:BUG-258 无前序同类记录。与 BUG-257 同为首次本地构建 staging 镜像时暴露的构建期问题。
|
||||
- 修复版本:未修复
|
||||
|
||||
Reference in New Issue
Block a user