Skip to content

GitHub Release body 超 125k 上限:spec 的 rc 段已达 343k,RC 窗口内每次发布都会在 createGithubReleases 处失败(npm 已发出、Docker 被连带跳过) #4900

Description

@os-zhuang

现象

Release run 30823919428(89d2a4e)第 16 步 Create Release Pull Request or Publish to npm 跑了 7 分 44 秒后 failure:

Everything up-to-date        ← 原子 tag 推送已完成,action 的逐个 tag push 全是 no-op(#2191 设计生效)
##[error]HttpError: Validation Failed:
  {"resource":"Release","code":"custom","field":"body",
   "message":"body is too long (maximum is 125000 characters)"}
  - https://docs.github.com/rest/releases/releases#create-a-release

失败发生在 npm 发布完成之后。实测注册表:

@objectstack/cli   dist-tags: {"latest":"16.1.0","rc":"17.0.0-rc.2"}
@objectstack/spec / runtime / console / create-objectstack  17.0.0-rc.2  均已发布

即包全发出去了、tag 也推了,只有 GitHub Release 创建失败 —— 但它把整个 step 标红,于是 published 输出没置为 'true',docker job 按 if: needs.release.outputs.published == 'true' 被跳过。17.0.0-rc.2 的运行时镜像因此没发(已用 docker-publish.yml 的 workflow_dispatch 手动补发)。

根因:changelog 段落早已超出 API 上限

packages/spec/CHANGELOG.md            1,517,277 字符(全文)
  └─ ## 17.0.0-rc.2 这一节           342,911 字符   ← 上限 125,000,超 2.7 倍

packages/cli/CHANGELOG.md               654,433
packages/runtime/CHANGELOG.md           591,339
packages/objectql/CHANGELOG.md          467,642

createGithubReleases: true 把对应版本的 changelog 段落原样塞进 Release body。spec 作为 fixed 组里条目最密集的包,rc 段已经远超上限。

这不是一次性故障:段落只会随 RC 窗口推进继续变大,所以窗口内每一次发布都会在同一处失败,并且每次都以同样的方式连带丢掉 Docker 镜像。17.0.0 GA 那一次只会更大。

可选方向(未决,需要维护者拍板)

  1. 自建 Release 并截断:不用 action 自带的 createGithubReleases,改为发布后自己调 API 建 Release,body 超限时截断并附 CHANGELOG.md 的链接/锚点。彻底消除这类失败,且保住 ADR-0087 D4 —— spec-changes.json 仍有 Release 可挂。改动最大。
  2. 只对超限的包截断:保留 action,但在它之前把超长的 changelog 段落改写成「摘要 + 链接」。会污染仓库里的 CHANGELOG 内容,不推荐。
  3. 关掉 createGithubReleases:最省事,但 ADR-0087 D4 的 spec-changes.json 是附到 spec 的 GitHub Release 上的,没有 Release 就没有落点 —— 需要先给那个产物找新家。

与 #4898 的关系

#4898/#4899 修的是「空 changeset 导致 action 根本不进 publish 分支」,与本 issue 是不同的失败:那次是压根没发,这次是发了但 Release 创建失败。两者的共同后果都是 published != 'true' → Docker 丢失,这一点由 #4899 引入的 recovery 步骤负责兜底 —— 但该步骤当前有两个缺陷,会另行修复:

  • 不带状态函数的 if: 被 GitHub 隐式包了一层 success(),所以 changesets 步骤失败后它被 skipped,恰恰漏掉了本 issue 这个场景;
  • 它的契约是「缺失就补发」,而今天 npm 上已有 rc.2,即使执行也是 no-op,不会把 published 置真、救不回 Docker。应改为守「当前版本必须在 npm 上且必须有对应镜像」这个不变量。

Activity

  1. claude commented on Aug 4, 2026

    @claude
    Contributor

    ⛔ 认领:session_015W6nhsDrz6zWQc8je12a1t(cloud 分片 + objectstack:identity 车道 PM,本条为维护者点名的跨车道代管),分支 claude/issue-4900-release-body-limit,os-dev 单点派发。

    维护者 2026-08-04 裁定:本条进 v17 前处理。 它不是功能缺陷而是发版机器本身的故障:RC 窗口内每一次发布都会在同一处失败(spec 的 rc 段 342,911 字符 vs 上限 125,000,超 2.7 倍),且每次都以同样方式连带丢掉 Docker 镜像;17.0.0 GA 那一次只会更大。

    方向取 1(自建 Release 并截断):不用 action 自带的 createGithubReleases,发布后自己调 API 建 Release,body 超限时截断并附 CHANGELOG 锚点链接。理由:方向 2 污染仓库里的 CHANGELOG 内容(本单自己不推荐),方向 3 会让 ADR-0087 D4 的 spec-changes.json 失去挂载点(需先给产物找新家,那是另一件事)。方向 1 彻底消除该类失败且保住 D4。

    同时按本单尾部记录修掉 #4899 recovery 步骤的两个缺陷:不带状态函数的 if: 被 GitHub 隐式包了 success()(导致 changesets 步骤失败时它被 skipped,恰好漏掉本场景);以及把它的契约从「缺失就补发」改为守「当前版本必须在 npm 上且必须有对应镜像」这个不变量。


    Generated by Claude Code

  2. claude commented on Aug 4, 2026

    @claude
    Contributor

    PM 验收(第 19 轮,objectstack):ACCEPT — PR #5290,CI 绿后合入

    按裁定的方向 1 落地,复核通过。两条最重的主张我在 GitHub API 上独立验证,均属实:

    tag 我实测的 HTTP
    @objectstack/spec@16.1.0 200
    @objectstack/spec@17.0.0-rc.0 404
    @objectstack/spec@17.0.0-rc.1 404
    @objectstack/spec@17.0.0-rc.2 404

    这比本单立案时的描述严重:我以为是 rc.2 那一次失败,实际上 spec 的 Release 从 rc.0 起就没建成过 —— gh release upload 需要一个已存在的 Release 作为落点,所以 ADR-0087 D4 的 spec-changes.json 在整个 v17 RC 窗口里一直是没挂上的。这条同时也是方向 3(直接关掉 createGithubReleases)不可行的实锤。另核实 #4901(db82f2e)确已在 main,dev 没有重复修那两个 recovery 缺陷,只在其上做扩展 —— 正确。

    验收要点:

    • 对 action 行为的忠实性有硬证据:body 提取是 getChangelogEntry 的逐行移植(含跳过代码围栏的逻辑,本仓 changeset 正文里真有 ## FROM → TO 这种二级标题),并拿线上真实的 @objectstack/cli@17.0.0-rc.2 release body 逐字节比对,73,993 字符完全一致。这是「不改变现有行为」这类主张少见的强证明。
    • 补上的三条性质都对:①有界 —— 按行边界截断,截断处若切开代码围栏会补闭合围栏(否则说明文字和 CHANGELOG 链接会被吞进代码块,截断唯一欠读者的东西恰好渲染不出来),不切代理对,计量用 UTF-16 code unit(对任何字符串 ≥ code point 数,无论 API 怎么理解「字符」都只会高估);②幂等 —— 先按 tag 查,存在 PATCH、不存在 POST,理由说得很实在:rc.2 那次 70 个里已经建成约 69 个,「在半成品集合上重跑」是常态不是例外;③按包隔离 —— action 原本把整组丢进一个 Promise.all,第一个 reject 就丢掉其余,所以某个包的坏 changelog 会连累 spec 拿不到 Release、D4 拿不到落点。顺序执行 + 收集失败 + 仍非零退出。
    • D4 的挂载点保住了,而且比原来强:新步骤排在 Attach spec-changes.json 之前(顺序 load-bearing,注释写明);release-spec-changes.sh 新增 RELEASE_VERSION 兜底,使恢复路径也能挂上 D4 —— 旧代码里那句 warning 明说「这条路径不产生 Release 和 D4 附件」,现在做到了。为此把 published(=docker 必须构建,原义未动)与新增的 npm-published(=这里确实发了包,因此欠 Release)拆成两个语义,version 改为无条件输出(原来只在「镜像缺失」分支才写,于是「npm 补发了但镜像恰好存在」这条路径上版本号会丢)。这几处的语义拆分是对的。
    • 顺带解掉 chore: version packages #2191 的成因:关掉 createGithubReleases 同时关掉了 action 自己的逐个 tag git push(它们在同一个 if 块内),而 release-publish.sh 早就用一次原子 git push --tags 抢在它前面 —— 那个 workaround 的成因本身没了(脚本保留)。这是收益不是损失,同意。
    • 验证走真实代码路径:pnpm check:release-body(= --self-test)49 条断言,用真实的 packages/spec/CHANGELOG.md rc.2 段落(342,893 → 123,938 ≤ 125,000);另有对着桩 GitHub API 跑真实 main()(按 GitHub 规则真的在 125,000 处返回 422)与全工作区 --dry-run(69 个包全部规划成功、只有 spec 一个被截断,其余 body 长度与线上已发布 release 逐个吻合:cli 73,993 / runtime 44,532 / types 6,433 / verify 10,426 / trigger-schedule 3,650)。
    • 证据边界诚实:新增两个 if: 的隐式 success() 行为依据 GitHub 文档与 ci(release): 让发布完整性守卫真的能触发,并把镜像也纳入它守的不变量 (#4900) #4901 已在真实 run 上确认的同一现象,未用 act 或真实 run 验证 —— 明说了。真正的证明是下一次发布。

    ⏳ 合入动作(draft→ready + auto-merge)需走 GraphQL,额度刚耗尽(数分钟后恢复),恢复即执行。


    Generated by Claude Code

  3. claude commented on Aug 4, 2026

    @claude
    Contributor

    PM 更正:关于 changeset 的返工指示,我错了 —— dev 的处置正确

    Check Changeset 红了之后,我发了返工单,并且在里面写「如果没有包需要发版就用空 frontmatter 的 changeset(门禁明说这是正式声明,与 skip-changeset 标签等价)」。这条指示是错的,dev 没有照做,而是用了 skip-changeset 标签,并把理由讲清楚了。我核实后确认它对:

    空 changeset 恰恰是 #4898 的致命输入。 #4898 的标题就写着「空 changeset 会静默卡死已 version 的发布:Release run 全绿,但 npm 和 Docker 什么都没发」——changesets/action 的分派逻辑在 hasChangesets && !hasNonEmptyChangesets 时走「All changesets are empty; not creating PR」那条 0 秒分支,于是版本号进了仓库、包没进注册表、流水线报绿。17.0.0-rc.2 就是这么卡住的。

    在一个专门修发布机器的 PR 里再种一个空 changeset,是本轮最不该犯的错。 门禁的提示文案把两者说成等价,但它们在 changesets/action 眼里根本不等价 —— 标签是闸门层面的豁免,空 changeset 是喂给 action 的真实输入。这个提示文案本身值得改(我会另立单记),但我不该照着它开药方。

    另一个选项(加非空 changeset)更差:会把 fixed 组 69 个包全部 bump 一版,在 RC 窗口内为一个纯 CI 修复凭空多发一个 rc 号,并在版本记录里留下一次并不存在的产物变更。

    skip-changeset 标签已应用(labels 现为 ci/cd, size/xl, dependencies, tooling, skip-changeset),同类的 #4899/#4901 也都是这么走的。验收维持 ACCEPT,CI 绿即合入。


    Generated by Claude Code

  4. added a commit that references this issue on Aug 4, 2026
  5. added a commit that references this issue on Aug 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions