Skip to content

fix(cli): os validate / os build 展开 invalid_union,联合分支里的处方到达终端 (#5341) - #5391

Merged
baozhoutao merged 2 commits into
mainfrom
claude/issue-5341-cli-format-zod-union
Aug 5, 2026
Merged

fix(cli): os validate / os build 展开 invalid_union,联合分支里的处方到达终端 (#5341)#5391
baozhoutao merged 2 commits into
mainfrom
claude/issue-5341-cli-format-zod-union

Conversation

@baozhoutao

Copy link
Copy Markdown
Contributor

Fixes #5341

问题

zod 把一个失败 union 的所有分支折进一条顶层 issue,它自己的 message 就是字面量 "Invalid input";每个分支真正的判决(必填缺失、未知键处方)挂在 issue.errors[] 上,路径相对于 union 自身。

CLI 的 formatZodErrors(packages/cli/src/utils/format.ts)只遍历顶层 issue —— 整个函数没有任何一处读 issue.errorsos validate / os build(compile) / os plugin build 三个命令都从这一个函数出终端,所以作者读到的是:

  views:
    ✗ views.0.list.sort
      invalid_union: Invalid input

而分支里那条 directionorder 的处方,每次都被生成、每次都没送达。终端因此是 #4001 战役里唯一一个策展散文到不了的读者面 —— 旁边的 --json payload 一直是全的。

修好之后:

  views:
    ✗ views.0.list.sort
      invalid_union: Invalid input
        ✗ views.0.list.sort.0.order: Invalid option: expected one of "asc"|"desc"
        ✗ views.0.list.sort.0: Unrecognized key(s) on this sort entry: `direction`. … Did you mean `direction` → `order`?

形态:复用,不是第三份实现

同一个缺陷的第三个消费者,三份各自独立的代码:

消费者 文件 状态
formatZodError(spec,defineStack 抛错走它) packages/spec/src/shared/error-map.zod.ts #4971 / PR #5342
zodIssuesToFields(REST wire) packages/rest/src/rest-server.ts #5014 / PR #5362
formatZodErrors(CLI 终端) packages/cli/src/utils/format.ts 本 PR

分支挑选策略沿用前两次已落地的那套,不重新发明:丢弃只说「值的种类不对」的分支、按 issue 最少挑最接近的那支、unrecognized_keys 破平局、绝对路径、深度上限。

区别在于复用方式#5362 当时是抄一份而不是 import,理由写在它自己的注释里:spec 只导出字符串渲染器,而 wire 要的是结构化的 {field, code, message}。终端要的恰好就是那个字符串,所以这里是一行 import { formatZodIssue } from '@objectstack/spec' —— 不改 spec,只消费它。一个错误不会因为作者是从终端发布还是 POST 到 API 而拿到三份不同的处方。

实现只有一个小函数 unionBranchLines():非 union 直接返回空;union 则取 formatZodIssue 的渲染结果,丢掉第 0 行(那是 union 自己的判决,本文件已用自己的排版打过了),其余行整体缩进 4 格挂到 invalid_union: Invalid input 之下。

严格增量

  • union 自己的 ✗ pathinvalid_union: Invalid input 两行照旧;
  • 非 union 的 issue 渲染逐字不变(code: / expected: / received: 都还在);
  • 页脚 N validation error(s) total 仍然数 error.issues —— 一个 union 无论用多少行解释都还是一条 issue,这正是页脚与旁边 --json payload 保持一致的原因;
  • --json 路径一个字节没动,它透传 error.issues,本来就是全的。

测试

新增 packages/cli/test/format-zod-union.test.ts(10 条):

反向验证(方向先声明,后执行)

把展开那一行去掉,预测 6 红 4 绿。实测完全一致:

× prints the failing branch prose under the union line
× drops the kind-mismatch branch that carries no prescription
× resolves branch paths against the union, not relative to it
× expands a union nested inside a union
× reports one unknown key ONCE, not once per branch
× prints the prescription, not a bare `invalid_union: Invalid input`
Tests  6 failed | 12 passed (18)

保持绿的 4 条是按设计该绿的:非 union 渲染、页脚计数、schema 层控制、--json 透传(第三条验收标准量的就是一条本 PR 不碰的路径)。

#5380 回归守卫

刚合并的 packages/cli/test/metadata-type-schema-gate.test.ts(B 组断言 os validate 输出含顶层 unrecognized_keys 的处方文本)全绿,反向验证那一轮里也全绿 —— 它量的是顶层路径,与 union 展开正交,本 PR 的输出形状变化没碰到它。

命令

pnpm --filter @objectstack/cli test    →  Test Files 73 passed (73) / Tests 670 passed (670)
pnpm --filter @objectstack/cli typecheck →  TYPECHECK_EXIT=0
node scripts/check-nul-bytes.mjs       →  OK (5407 files)
eslint(改动两文件,--no-inline-config) →  0 问题

(已合并 origin/main,含刚落地的 #5381 —— 同包不同文件,按 AGENTS.md §10 在合并树上重跑了整包。)

前提核对(以 origin/main 为准)

issue 的核心论断成立:formatZodErrors 确实只遍历顶层,三个调用点行号也没漂(validate.ts:101 / compile.ts:162 / plugin/build.ts:114)。

一处需要更正:issue 正文举的 compareTo 例子今天点不着 —— #5011 已经把 compareTo 从 union 收敛成 plain strict object(dashboard-compareto.test.ts 里有「产生 NO invalid_union issue」的钉),正是为了绕开这个缺陷。缺陷本身与那个例子无关,所以本 PR 换用仍然活着的 views[].list.sort 作标本复现。

顺带

issue 正文里那条休眠注记(invalid_key / invalid_element 把真实 issue 挂在 issue.issues 上,三个消费者一个都不下降;今天 z.record(...) 的键 schema 全是 z.string() 或 enum,所以点不着)已单独存档为 #5389(finding,未认领),免得随本单关闭一起埋掉。不在本 PR 范围


🤖 Generated with Claude Code

https://claude.ai/code/session_01VkPSGsX9o17MsGv3Lbxu2w


Generated by Claude Code

claude added 2 commits August 5, 2026 04:17
…ion reaches the terminal (#5341)

Zod folds every branch of a failed union into ONE top-level issue whose own
message is the literal "Invalid input"; each branch's real rejection sits in
`issue.errors[]`. The CLI's `formatZodErrors` walked only the top level, so
`os validate`, `os build` (compile) and `os plugin build` — all three print
through that one function — showed `invalid_union: Invalid input` and dropped
the branch that says WHICH key is wrong.

Third consumer of the same defect after `formatZodError` (#4971, PR #5342) and
`zodIssuesToFields` (#5014, PR #5362). The branch-selection policy is reused
rather than re-derived: because the terminal needs exactly the string spec
already exports, this one is a plain `formatZodIssue` import instead of a third
copy of the ranking.

Strictly additive: the union's own lines still print, non-union issues render
unchanged, the footer still counts `error.issues`, and the `--json` path is
untouched.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01VkPSGsX9o17MsGv3Lbxu2w
@vercel

vercel Bot commented Aug 5, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
objectstack Ignored Ignored Aug 5, 2026 4:24am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Aug 5, 2026
@github-actions

github-actions Bot commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/cli.

21 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/ai/skills-reference.mdx (via packages/cli)
  • content/docs/api/client-sdk.mdx (via @objectstack/cli)
  • content/docs/api/data-flow.mdx (via @objectstack/cli)
  • content/docs/api/environment-routing.mdx (via @objectstack/cli)
  • content/docs/api/error-catalog.mdx (via @objectstack/cli)
  • content/docs/automation/hook-bodies.mdx (via packages/cli)
  • content/docs/deployment/backup-restore.mdx (via @objectstack/cli)
  • content/docs/deployment/cli.mdx (via @objectstack/cli)
  • content/docs/deployment/self-hosting.mdx (via @objectstack/cli)
  • content/docs/deployment/validating-metadata.mdx (via packages/cli)
  • content/docs/getting-started/your-first-project.mdx (via @objectstack/cli)
  • content/docs/kernel/runtime-services/data-service.mdx (via packages/cli)
  • content/docs/kernel/runtime-services/index.mdx (via packages/cli)
  • content/docs/permissions/authentication.mdx (via @objectstack/cli)
  • content/docs/plugins/index.mdx (via @objectstack/cli)
  • content/docs/plugins/packages.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/plugin-spec.mdx (via @objectstack/cli)
  • content/docs/protocol/kernel/realtime-protocol.mdx (via @objectstack/cli)
  • content/docs/releases/implementation-status.mdx (via @objectstack/cli)
  • content/docs/releases/v16.mdx (via @objectstack/cli)
  • content/docs/releases/v17.mdx (via @objectstack/cli)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

@github-actions github-actions Bot added the size/m label Aug 5, 2026
@baozhoutao
baozhoutao marked this pull request as ready for review August 5, 2026 04:26
@baozhoutao
baozhoutao enabled auto-merge August 5, 2026 04:26
@baozhoutao
baozhoutao added this pull request to the merge queue Aug 5, 2026
Merged via the queue into main with commit 3d94141 Aug 5, 2026
24 checks passed
@baozhoutao
baozhoutao deleted the claude/issue-5341-cli-format-zod-union branch August 5, 2026 04:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

os validate / os build 用的是 CLI 自己的 formatZodErrors,它同样把 union 分支的处方裁掉 —— #4971 修的不是这条路径

2 participants