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

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.errors。os validate / os build(compile) / os plugin build 三个命令都从这一个函数出终端,所以作者读到的是:

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

而分支里那条 direction → order 的处方,每次都被生成、每次都没送达。终端因此是 #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 自己的 ✗ path 与 invalid_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.

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