fix(memory): tolerate null/empty operation field via zod catch - #2112
Open
pansz wants to merge 1 commit into
Open
fix(memory): tolerate null/empty operation field via zod catch#2112pansz wants to merge 1 commit into
pansz wants to merge 1 commit into
Conversation
The memory tool operation schema is z.enum(["search"]).default("search").
Serialized to JSON Schema, this becomes a field that is simultaneously
required, has a default, and has an enum with a single value — a
contradictory shape that some reasoning models (notably MiniMax-M3)
interpret as "send null to mean use the default". zod .default() fires
for undefined/missing keys but not for explicit null or "", so those
calls fail with "Invalid input: expected \"search\"" at the operation
path before the tool ever runs.
Add .catch("search") so any invalid operation value falls back to the
only legal value, matching the schema semantic intent (search is the
only operation). One-line change; the enum, default, and describe are
unchanged, so upstream refactors that touch this field stay
patch-friendly.
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.
Summary / 概要
The
memorytool'soperationparameter is declared asz.enum(["search"]).default("search")inpackages/opencode/src/tool/memory.ts. Serialized to JSON Schema, this becomes a field that is simultaneouslyrequired, has adefault, and has anenumwith a single value — a contradictory shape that some reasoning models interpret as "sendnullto mean 'use the default'".zod's.default()fires for missing keys and explicitundefined, but not for explicitnullor"". Calls from those models fail at the parameter-validation step with:…before the tool ever runs. Most models (Claude / GPT / Gemini) emit
"search"correctly and never hit this path, which is why the bug has been latent since the initial open-source release and only surfaces on certain reasoning-model providers (e.g. MiniMax-M3).memory工具的operation参数在packages/opencode/src/tool/memory.ts中声明为z.enum(["search"]).default("search")。序列化到 JSON Schema 时,这个字段同时是required、有default、且enum只有一个值——一种自相矛盾的形态。部分推理类模型会把它解读成"发null等于使用默认值"。zod 的
.default()会在字段缺失或显式undefined时触发,但对显式null或""不会触发。来自这些模型的调用会在参数校验阶段直接失败(错误如上),工具根本无法运行。Claude / GPT / Gemini 等大多数模型会正确发出"search",所以这个 bug 从开源初始版本起就一直潜伏,只在部分推理模型(例如 MiniMax-M3)上才暴露。Reproduction / 复现
Verified against
zod@4.1.8(catalog version, unchanged from prior releases).在
zod@4.1.8(catalog 版本,自此前版本未变)下验证。Fix / 修复
One-line change — add
.catch("search")between.default(...)and.describe(...):一行修改——在
.default(...)和.describe(...)之间加.catch("search"):.catch("search")swallows any parse failure on this field and substitutes"search"— the only legal value, which matches the schema's semantic intent ("search is the only operation"). The enum, default, and describe are all preserved, so any future refactor of this field stays patch-friendly..catch("search")吞掉这个字段上所有解析失败并替换为"search"——即唯一合法值,与 schema 的语义意图一致("search 是唯一的操作")。enum、default、describe 都保留,未来如果有人重构这个字段,本补丁依然可以轻松叠上去。Verification / 验证
{operation: "search", ...}{...}(field omitted / 字段省略){operation: undefined, ...}{operation: null, ...}expected "search""search"{operation: "", ...}expected "search""search"Manually tested in TUI with
minimax-cn-coding-plan/MiniMax-M3— the failing call now returns search results instead ofInvalid input: expected "search".在 TUI 中用
minimax-cn-coding-plan/MiniMax-M3实测:原本失败的调用现在能正常返回搜索结果,而不是Invalid input: expected "search"。Notes / 备注
The schema, default, and describe are all unchanged; this is purely a defensive fallback that only fires when the upstream value is unparseable. It does not alter behavior for any model that already emits
"search"correctly.Alternative considered: remove the
operationfield entirely. Rejected because it is a structural breaking change for any tool consumer / future schema work; the catch keeps the schema shape stable.schema、default、describe 全部未改;这只是一个防御性的兜底,只在上游值无法解析时才会触发。对任何已经能正确发出
"search"的模型,行为完全不变。备选方案:直接把
operation字段从 schema 里移除。否决理由:这是结构性破坏性变更,对未来工具消费者与 schema 演进不友好;.catch方案保持了 schema 形状的稳定。