fix(core): drop top-level oneOf from send_message tool schema - #7989
Conversation
Anthropic's Messages API rejects any tool `input_schema` carrying a
top-level `oneOf`/`allOf`/`anyOf` combinator with a hard 400:
input_schema does not support oneOf, allOf, or anyOf at the top level
send_message's schema used `oneOf: [{ required: ['to'] }, { required:
['task_id'] }]` to express "either a teammate recipient or a
background-task id is required." Since this schema is forwarded
verbatim as `tools[].input_schema` to Anthropic
(anthropicContentGenerator/converter.ts -> convertSchema), every call
to send_message on an Anthropic-backed model (native API, Vertex, or
an Anthropic-compatible proxy) failed outright -- not degraded, fully
unusable.
Verified live against the Anthropic Messages API (claude-sonnet-5):
the schema with `oneOf` 400s with the exact message above; the
identical request with `oneOf` removed succeeds (200, tool_use
returned).
The `oneOf` constraint was redundant defense-in-depth, not the only
enforcement: send_message's execute() already returns a clear runtime
error ("No active team and no task_id provided...") when neither `to`
nor `task_id` is supplied. Dropping the schema-level constraint leaves
that runtime validation intact and unchanged for OpenAI/Gemini-backed
models, while making the tool actually callable on Anthropic-backed
models.
Fixes QwenLM#7984
|
✅ Qwen Triage finished — CI landed green on ✅ Qwen Triage 已完成 —— |
|
|
|
Thanks for the PR — and for the clean reproduction in #7984. Template: the body uses its own headings ("Verification", "Demo") rather than the template's "Reviewer Test Plan / How to verify / Evidence / Tested on / Risk & Scope" structure, and there's no Chinese Problem: real and observed, not theoretical. #7984 includes a live Direction: aligned. A tool that 400s before the model ever sees it is a clear bug, and Anthropic's rejection of top-level combinators is a well-known, documented constraint. Size: touches a core path ( Approach: this is the minimal fix. The Risk: no elevated risk signals — Moving on to code review. 🔍 中文说明感谢贡献,也感谢 #7984 里清晰的复现。 模板: PR 正文用了自己的小标题("Verification"、"Demo"),而非模板的 "Reviewer Test Plan / How to verify / Evidence / Tested on / Risk & Scope" 结构,也没有中文 问题: 真实且已观测,不是理论问题。#7984 包含对 Anthropic Messages API 的 live 方向: 对齐。一个在模型看到之前就 400 的工具是明确的 bug,Anthropic 拒绝顶层组合关键字是已知的、有文档的约束。 规模: 触及核心路径( 方案: 这是最小修复。该 风险: 无升级风险信号—— 进入代码审查 🔍 — Qwen Code · qwen3.8-max-preview Reviewed at |
Code reviewReading just the title and motivation, my own proposal for "top-level The diff is the whole change — six deleted lines (the constraint plus its explanatory comment), nothing else touched. No correctness, security, or regression concerns:
Nothing to flag. Test evidence (PR's own CI)CI on the reviewed commit is still settling — the Linux unit suite is mid-run, so I'm reporting what's there now rather than polling. The finalize job will refresh the table below once CI completes. Final CI results for
One row per check name (latest run); skipped checks omitted; failures sort first. / 每个检查名一行(取最新一次运行),省略 skipped,失败项排在最前。 The unit suite passing wouldn't by itself prove the fix — no test asserts on the wire schema, so the suite is green with or without the The sandboxed verification lanes ( 中文说明代码审查只看标题和动机,我对"顶层 diff 就是全部改动——删除六行(约束及其解释注释),没有动其他地方。无正确性、安全或回归隐患:
无可指摘。 测试证据(PR 自身 CI)被审 commit 上的 CI 仍在进行——Linux 单元测试套件正在运行,所以我报告当前状态而非轮询。CI 完成后 finalize 任务会刷新下表。 单元测试通过本身并不能证明修复——没有测试断言 wire schema,所以无论有没有 沙盒验证通道( — Qwen Code · qwen3.8-max-preview Reviewed at |
|
Confidence: 5/5 — a textbook minimal fix: real, reproduced bug; six deleted lines; runtime enforcement untouched; nothing else in the diff. Stepping back, this is exactly the kind of PR you want to see. The problem is unambiguous — The only thing I can't attest to yet is CI — the Linux unit suite is still running on the reviewed commit. The verdict is approve, but I'm not posting the approval while CI is in flight; that would attest to a result that doesn't exist yet. Approval is deferred until CI lands green on 中文说明置信度:5/5 —— 教科书式的最小修复:真实且有复现的 bug;删除六行;运行时强制未动;diff 里没有别的东西。 退一步看,这正是你想看到的 PR。问题明确—— 我唯一还不能背书的是 CI——被审 commit 上的 Linux 单元测试套件仍在运行。结论是 approve,但我不会在 CI 进行时提交批准;那等于为一个尚不存在的结果背书。批准推迟到 CI 在 — Qwen Code · qwen3.8-max-preview Reviewed at |
qwen-code-ci-bot
left a comment
There was a problem hiding this comment.
LGTM, looks ready to ship — CI landed green after the review. ✅
Maintainer verification — built and tested locally at
|
|
Released in v0.21.2. |



What this PR does
Removes the top-level
oneOfcombinator fromsend_message's toolinput_schema. Anthropic's Messages API rejects any tool schemacarrying a top-level
oneOf/allOf/anyOfwith a hard 400, so thismade
send_messagecompletely unusable on every Anthropic-backedmodel (native API, Vertex, or an Anthropic-compatible proxy).
Why it is needed
Fixes #7984.
send_message's schema usedoneOf: [{ required: ['to'] }, { required: ['task_id'] }]to express "either a teammate recipient or abackground-task id is required." This schema is forwarded verbatim as
tools[].input_schemato Anthropic inpackages/core/src/core/anthropicContentGenerator/converter.ts(
convertSchema→input_schema), and Anthropic's tool-schemavalidator does not accept
oneOfat the schema root — every call400s before the model even sees the tool.
The
oneOfconstraint was redundant, not the sole enforcement:send_message'sexecute()already returns a clear runtime error("No active team and no task_id provided...") when neither
tonortask_idis supplied. Removing it from the JSON Schema leaves thatruntime check intact and unchanged for OpenAI/Gemini-backed models,
while making the tool actually callable on Anthropic-backed models.
Verification
Reproduced against the live Anthropic Messages API with
claude-sonnet-5, isolating the exact schema shape:Request with the
oneOfconstraint (currentmain):{ "type": "error", "error": { "type": "invalid_request_error", "message": "tools.0.custom.input_schema: input_schema does not support oneOf, allOf, or anyOf at the top level" } }Identical request with
oneOfremoved (this PR):200,tool_usereturned successfully.
packages/core/src/tools/send-message.test.ts(20 tests) passesunchanged — no test asserted on the
oneOffield, and the tool'sruntime "missing recipient" behavior is untouched.
Also ran, clean:
npx tsc --noEmit -p packages/core/tsconfig.json(0 errors)npx eslint packages/core/src/tools/send-message.ts(0 warnings)npx prettier --check packages/core/src/tools/send-message.tspre-commitlint-staged hook (prettier + eslint --fix--max-warnings 0) on the commit
Demo
N/A — internal tool schema fix, no user-facing UI. Behavioral
before/after is the curl reproduction above (400 → 200) rather than a
screenshot, since this is a wire-level API compatibility fix.