fix(spec): formatZodError 展开 union 分支的拒绝信息 (#4971) - #5342
Merged
Conversation
zod 对失败的 union 只抛一个 `invalid_union` issue,其 message 是字面量 "Invalid input",各分支的真实 issue 嵌在 `issue.errors[]` 里。 `formatZodError` 只 map 顶层 `error.issues`,从不下降,于是 union 后面 每一个 strictObject 的策展散文在 CLI 路径(`os validate` / `os compile`) 上都被裁成 `✗ (root): Invalid input`。REST 错误体和 `ZodError.message` 一直带着这份 payload —— 丢的只是压成单行的消费者。 现在按信息量挑分支展开:只报"值的种类不对"的分支(union 里 z.string() 成员对着对象喊 expected string)不携带处方,直接丢弃;全部分支都是这一类 时(z.union([z.string(), z.number()]))完全不展开,输出与改前逐字节相同。 其余分支里 issue 最少的胜出 —— 作者真正想写的那个成员只抱怨那一个多余的 键,其它成员还会抱怨判别式和自己的必填项 —— 这正是"一个未知键不被报 N 次" (#4001 批 6c 的回归)的机制;`unrecognized_keys` 破平局,声明顺序兜底。 真正打平的分支全部渲染(上限 3),跨分支相同的判决只印一次。嵌套 union 递归展开,路径为绝对路径,深度上限 3 层。 表头的 issue 数仍是 `error.issues.length`,CLI 与结构化消费者对"错了几处" 保持同一口径。 - packages/spec/src/shared/error-map.zod.ts: 分支选择 + 递归渲染 - packages/spec/src/shared/error-map.test.ts: 9 条新钉(含 6 条改前必红) - packages/spec/src/automation/state-machine.test.ts: 批 10 的 CONTROL 钉 按其自述翻转 - packages/spec/src/automation/state-machine.zod.ts: 仅注释,原文陈述的 "formatZodError 是丢弃者之一"已不再成立 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTszibd6C8sUCCZnM4VcrL
|
The latest updates on your projects. Learn more about Vercel for GitHub. 1 Skipped Deployment
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 107 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
selectUnionBranches 的 UNION_BRANCH_RENDER_LIMIT 与 omitted 提示行此前 没有测试到达:需要 3 个以上分支真正打平才会触发。五个互不相交的 strictObject 分支对同一个多余键正好构成这种平局。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTszibd6C8sUCCZnM4VcrL
os validate / os build 走的是 CLI 自己的 formatZodErrors,不是这里的 formatZodError;后者的实际到达面是 defineStack 的抛错。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTszibd6C8sUCCZnM4VcrL
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTszibd6C8sUCCZnM4VcrL
os validate / os build 的 schema 报错走 CLI 自己的格式化器;本包这份的 到达面是 defineStack 的抛错。 Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FTszibd6C8sUCCZnM4VcrL
os-zhuang
marked this pull request as ready for review
August 5, 2026 00:23
This was referenced Aug 5, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4971
前提复核(先证,再改)
issue 的核心断言在
origin/main(c7406b0)上成立,并且是实测的:formatZodError只map顶层error.issues,从不下降进issue.errors[](改前error-map.zod.ts:186)。处方一直在 payload 里,丢的只是压成单行的消费者。一处需要更正的前提(不影响本单结论):issue 说「
formatZodError的文档用途正是 CLI 输出(os validate/os compile)」—— JSDoc 确实这么写,但那两个命令实际调的是 CLI 自己的formatZodErrors(packages/cli/src/utils/format.ts:167),它有同样的缺陷。已另单记录为 #5341(不在本单文件面内)。本单的修复并非空转:defineStack的严格模式抛错走的就是formatZodError(packages/spec/src/stack.zod.ts:1250),而任何加载 stack 配置的命令都会执行它 —— 作者在 union 里写错一个键,今天起就能读到处方。改了什么
formatZodIssue对code === 'invalid_union'递归展开issue.errors,缩进一层、路径拼成绝对路径:全印一遍是本单唯一必须避免的失败模式(#4001 批 6c:一个未知键被 N 个分支各报一次,
view.zod.ts的submitBehavior因此改用discriminatedUnion),所以分支是挑的,不是倒的:z.union([z.string(), SomeObject])里z.string()那支喊expected string, received object)不携带处方,丢弃。当所有分支都是这一类时(z.union([z.string(), z.number()])收到一个对象)完全不展开,输出与改前逐字节相同 —— 原始类型 union 是全仓最常见的 union,给它加 N 行噪音换零处方是负收益。unrecognized_keys破平局(策展散文在那儿),声明顺序兜底 —— 输出确定。表头的 issue 数仍是
error.issues.length—— union 无论用几行解释都是一条 issue,CLI 与结构化消费者(REST 错误体、ZodError.message)对「错了几处」保持同一口径。反向验证(方向:红,如期)
先写断言、临时把
error-map.zod.ts还原成origin/main再跑:上表 7 条 = 6 条新钉 + 1 条被翻转的 CONTROL 钉。第 7 条新钉(
caps a wide tie and says how many branches it did not print,后补,钉的是渲染上限那条此前无人到达的分支)单独按同样方式量过:所以新增的 10 条钉里 7 条改前必红,全部实测;另外 3 条(
drops the kind-mismatch branch…、leaves an all-kind-mismatch union exactly as it was、counts the union as the one issue zod raised)两边都绿是设计如此 —— 它们钉的是「不许变」的不变量(原始类型 union 输出不动、表头计数不动),不是本次的行为变更,如实记在这里而不是凑成「全红转全绿」。批 10 预言的那条钉(
state-machine.test.ts的 CONTROL)按其自述翻转:改前断言not.toContain('this action reference'),现在断言它在,并额外钉住expected string这类噪音不被打印。消费半径普查
formatZodError/formatZodIssue/safeParsePretty在本仓的唯一非测试调用点是packages/spec/src/stack.zod.ts:1250(defineStack);objectui/cloud两个兄弟仓零引用(全文扫描)。packages/cli与packages/core各有同名的本地格式化器,不经过这里(见 #5341)。packages/spec全量测试通过,说明没有第二条钉在这次输出变化上变红。相关但不在本 PR 里修
zodIssuesToFields只映射顶层 issue,失败的 union 只剩Invalid input#5014(同族,packages/rest的zodIssuesToFields)—— wire 契约改动,按派发另单处理,本 PR 不碰packages/rest/**。os validate/os build用的是 CLI 自己的 formatZodErrors,它同样把 union 分支的处方裁掉 —— #4971 修的不是这条路径 #5341(本次新记):os validate/os build/os plugin build真正走的 CLI 本地格式化器有同样缺陷;顺带记了invalid_key/invalid_element的同类休眠缺口。docs/audits/2026-07-unknown-key-strictness-ledger.md的state-machine.zod.ts行还写着「formatZodError把它压掉 —— 已记录,未在此修」,现已过时。该文件是 spec 车道的独占面,本 PR 不手改,留给该车道随手更新。文件面
packages/spec/src/shared/error-map.zod.ts—— 分支选择 + 递归渲染packages/spec/src/shared/error-map.test.ts—— 10 条新钉packages/spec/src/automation/state-machine.test.ts—— CONTROL 钉翻转packages/spec/src/automation/state-machine.zod.ts—— 仅注释(声明的文件面外一处):该 JSDoc 原文陈述「formatZodError是丢弃者之一,filed 而非在此修」,本 PR 之后这句是假的,留着会误导下一个读者.changeset/format-zod-error-union-branches.md——@objectstack/spec: minor(公开导出的输出行为变化)验证
🤖 Generated with Claude Code
https://claude.ai/code/session_01FTszibd6C8sUCCZnM4VcrL