fix(translate): bail to non-strict for bare-{} optional tool args#830
Open
Symbiomancer wants to merge 1 commit into
Open
fix(translate): bail to non-strict for bare-{} optional tool args#830Symbiomancer wants to merge 1 commit into
Symbiomancer wants to merge 1 commit into
Conversation
#829 fixed the Workflow.args 400 by having makeNullable synthesize a six-type union branch for a typeless optional, keeping the tool strict. But that branch admits "object", and OpenAI strict mode then requires the node to carry additionalProperties:false — which the synthetic branch lacks — so the request still 400'd, now at context=('properties','args','anyOf','0','type','3'). A bare {} "any JSON value" has no strict representation: closing it with additionalProperties:false would forbid the arbitrary keys the parameter exists to accept, and narrowing the type union would silently drop valid object/array args. So strictifyNode now bails when an optional property has no strict-expressible type, and the whole tool falls back to non-strict emission per this file's documented fail-open contract. Reverts makeNullable's typeless fallback to wrapping the node, now unreachable behind the schemaHasStrictType guard. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Claude finished @Symbiomancer's task —— View job Advisory comment-length review posted on the PR diff. |
workweave-bot
left a comment
Collaborator
There was a problem hiding this comment.
Advisory only — comment-length nits. Won't block merge.
Comment on lines
+171
to
+178
| // An optional property with no strict-expressible type (a bare {} | ||
| // "any JSON value", e.g. Workflow.args) cannot be made nullable | ||
| // without either admitting "object" — which strict mode then | ||
| // requires to carry additionalProperties:false, forbidding the | ||
| // arbitrary keys the schema exists to accept — or narrowing the | ||
| // value space and silently dropping valid object/array args. | ||
| // There is no strict representation of an open value, so bail to | ||
| // non-strict emission per this file's fail-open contract. |
Collaborator
There was a problem hiding this comment.
Suggested change
| // An optional property with no strict-expressible type (a bare {} | |
| // "any JSON value", e.g. Workflow.args) cannot be made nullable | |
| // without either admitting "object" — which strict mode then | |
| // requires to carry additionalProperties:false, forbidding the | |
| // arbitrary keys the schema exists to accept — or narrowing the | |
| // value space and silently dropping valid object/array args. | |
| // There is no strict representation of an open value, so bail to | |
| // non-strict emission per this file's fail-open contract. | |
| // A bare {} optional (open value) has no valid strict form: admitting | |
| // "object" requires additionalProperties:false (forbidding arbitrary keys), | |
| // and narrowing to non-object types drops valid structured args. Bail. |
8 lines explaining a 2-step constraint; the WHY fits in 3.
Comment on lines
+220
to
+223
| // No type and no anyOf (e.g. bare enum): wrap the node itself. Callers must | ||
| // not pass a fully typeless node here — strictifyNode bails on typeless | ||
| // optionals before reaching this point, since an open value has no strict | ||
| // representation (see the schemaHasStrictType guard in strictifyNode). |
Collaborator
There was a problem hiding this comment.
Suggested change
| // No type and no anyOf (e.g. bare enum): wrap the node itself. Callers must | |
| // not pass a fully typeless node here — strictifyNode bails on typeless | |
| // optionals before reaching this point, since an open value has no strict | |
| // representation (see the schemaHasStrictType guard in strictifyNode). | |
| // No type and no anyOf (e.g. bare enum): wrap node. Typeless optionals | |
| // are rejected by strictifyNode before reaching here. |
4 lines; the caller-contract note and cross-reference both fit in 2.
Comment on lines
+239
to
+243
| // Workflow.args is a bare {} "any JSON value" optional. #829 kept it strict by | ||
| // synthesizing a 6-type union branch, but that branch admits "object" without | ||
| // additionalProperties:false, so OpenAI strict mode still 400'd | ||
| // (context=('properties','args','anyOf','0','type','3')). An open value has no | ||
| // strict representation, so the whole tool must bail to non-strict emission. |
Collaborator
There was a problem hiding this comment.
Suggested change
| // Workflow.args is a bare {} "any JSON value" optional. #829 kept it strict by | |
| // synthesizing a 6-type union branch, but that branch admits "object" without | |
| // additionalProperties:false, so OpenAI strict mode still 400'd | |
| // (context=('properties','args','anyOf','0','type','3')). An open value has no | |
| // strict representation, so the whole tool must bail to non-strict emission. | |
| // Workflow.args is a bare {} optional; an open value has no strict form | |
| // (#829's 6-type union still 400'd because "object" needs additionalProperties:false). |
5 lines; the key WHY fits in 2.
What T-Rex did
Reviews (1): Last reviewed commit: "fix(translate): bail to non-strict for b..." | Re-trigger Greptile |
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.
Problem
Claude Code sessions routed to an OpenAI Responses-path model (gpt-5.x) were dying on the first turn that carried the
Workflowtool:Workflow.argsis a bare{}"any JSON value" optional parameter. #829 addressed an earlier form of this 400 by havingmakeNullablesynthesize a six-type union branch (["string","number","boolean","object","array","null"]) so the tool could staystrict:true. But that union admits"object", and OpenAI strict mode then requires the node to also carryadditionalProperties:false— which the synthetic branch lacks. So the request still 400s, just one level deeper (note the error path lands onanyOf.0.type.3, i.e. index 3 ="object"of the synthesized union).Why not just add
additionalProperties:falseto the synthetic branch?Because a bare
{}has no valid strict representation:additionalProperties:falseand nopropertiesconstrainsargsto an empty object only — breaking a parameter whose entire purpose is passing arbitrary JSON.object/arraysilently drops valid structured args.An open value is exactly the case
strictifyis meant to bail on and fall back to non-strict emission (the file's documented fail-open contract). Non-strict emission is already the path foroneOf, unresolved$ref, tuple items, etc., andtoolcheckstill validates tool calls against the original schema downstream.Change
strictifyNode: when an optional property has no strict-expressible type (!schemaHasStrictType), returnok=falseso the whole tool emits non-strict, instead of lettingmakeNullablesynthesize an unrepresentable branch.makeNullable: revert the fix(translate): bail strict-mode strictification for typeless optional properties #829 typeless fallback back to wrapping the node inanyOf+ null. That path is now unreachable behind the new guard; the comment says so.TestStrictify_TypelessOptionalGetsExplicitValueType(asserted the now-wrong "stays strict" contract) withTestStrictify_TypelessOptionalBails.Validation
go vet ./internal/translate/— cleango test ./internal/translate/...— pass (translate + toolcheck)wv mr tc— cleanwv mr t(full router suite) — pass🤖 Generated with Claude Code