Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Zod schema constraints that Anthropic rejects or silently ignores when sent as structured-output tool definitions via Output.object(). Use when writing or reviewing Zod schemas passed to Output.object(), or debugging structured-output validation errors.
.claude/skills/growthxai-llm-output-schema-constraints/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-02 | ✗→✓ | ▲ Improved | 17% | 0% |
| case-11 | ✗→✓ | ▲ Improved | -23% | 0% |
| case-03 | ✗→✓ | ▲ Improved | 0% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 46% | 0% |
| case-05 | ✗→✓ | ▲ Improved | -29% | 0% |
When using Output.object() with generateText, the Zod schema is converted to JSON Schema and sent to the LLM provider as a tool definition. Anthropic does not support many JSON Schema constraints, which means certain Zod methods will cause errors or be silently ignored when the schema is sent to the provider.
Numbers: .min(), .max() on z.number() produce minimum/maximum — rejected by Anthropic.
Arrays: .min(), .max(), .length() on z.array() produce minItems/maxItems — Anthropic only supports minItems of 0 or 1. Any other value (e.g. .length(3), .min(2)) will be rejected.
.describe() instead of numeric/array constraints for LLM output schemastypescript// LLM output schema - sent to provider via Output.object() output: Output.object( { schema: z.object( { score: z.number().describe( 'Quality score 0-100' ), predictions: z.array( predictionSchema ).describe( 'Exactly 3 predictions' ) } ) } )
typescript// Workflow/evaluator validation schema - Zod-only, NOT sent to LLM export const workflowOutputSchema = z.object( { score: z.number().min( 0 ).max( 100 ).describe( 'Quality score 0-100' ), predictions: z.array( predictionSchema ).length( 3 ).describe( 'Exactly 3 predictions' ) } );
| Context | .min()/.max()/.length() | .describe() | |---------|:-:|:-:| | Schema passed to Output.object() | No (numbers or arrays) | Yes | | inputSchema / outputSchema on workflows | OK | Optional | | outputSchema on evaluators | OK | Optional | | workflowOutputSchema in types.ts | OK | Optional |
The .describe() annotation guides the LLM on expected ranges and counts. The .min()/.max()/.length() constraints are for runtime Zod validation only and should be used on schemas that validate data within your application, not schemas sent to LLM providers.
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-01 | pass→pass | 14,336 | 8,355 | -42% | 1 | 1 | 0% | 2,502 | 2,070 | -17% | 0 | 0 | — |
case-02 | fail→pass | 4,820 | 2,693 | -44% | 1 | 1 | 0% | 799 | 934 | +17% | 0 | 0 | — |
case-11 | fail→pass | 10,809 | 4,902 | -55% | 1 | 1 | 0% | 1,917 | 1,467 | -23% | 0 | 0 | — |
case-03 | fail→pass | 7,519 | 4,747 | -37% | 1 | 1 | 0% | 1,212 | 1,214 | +0% | 0 | 0 | — |
case-04 | fail→pass | 5,471 | 5,329 | -3% | 1 | 1 | 0% | 942 | 1,377 | +46% | 0 | 0 | — |
case-05 | fail→pass | 10,412 | 4,359 | -58% | 1 | 1 | 0% | 1,770 | 1,254 | -29% | 0 | 0 | — |
case-06 | pass→pass | 10,245 | 4,313 | -58% | 1 | 1 | 0% | 1,728 | 1,202 | -30% | 0 | 0 | — |
case-07 | pass→pass | 8,640 | 5,515 | -36% | 1 | 1 | 0% | 1,597 | 1,473 | -8% | 0 | 0 | — |
case-08 | pass→pass | 6,598 | 5,141 | -22% | 1 | 1 | 0% | 1,148 | 1,354 | +18% | 0 | 0 | — |
case-09 | pass→pass | 19,170 | 7,683 | -60% | 1 | 1 | 0% | 2,875 | 1,788 | -38% | 0 | 0 | — |
case-10 | fail→pass | 13,745 | 9,265 | -33% | 1 | 1 | 0% | 2,359 | 2,062 | -13% | 0 | 0 | — |
case-12 | fail→pass | 7,729 | 3,930 | -49% | 1 | 1 | 0% | 1,351 | 1,186 | -12% | 0 | 0 | — |
case-13 | fail→pass | 4,461 | 4,673 | +5% | 1 | 1 | 0% | 822 | 1,231 | +50% | 0 | 0 | — |
case-14 | fail→pass | 40,749 | 10,609 | -74% | 1 | 1 | 0% | 1,384 | 2,433 | +76% | 0 | 0 | — |
case-15 | fail→pass | 5,444 | 4,590 | -16% | 1 | 1 | 0% | 1,033 | 1,256 | +22% | 0 | 0 | — |
case-16 | fail→pass | 13,857 | 4,755 | -66% | 1 | 1 | 0% | 2,210 | 1,474 | -33% | 0 | 0 | — |
case-17 | fail→pass | 5,399 | 3,264 | -40% | 1 | 1 | 0% | 918 | 1,094 | +19% | 0 | 0 | — |
case-18 | fail→pass | 7,829 | 6,246 | -20% | 1 | 1 | 0% | 1,365 | 1,657 | +21% | 0 | 0 | — |
case-19 | fail→pass | 21,522 | 4,866 | -77% | 1 | 1 | 0% | 1,054 | 1,431 | +36% | 0 | 0 | — |
case-20 | fail→pass | 6,467 | 5,047 | -22% | 1 | 1 | 0% | 996 | 1,299 | +30% | 0 | 0 | — |
case-21 | fail→pass | 6,170 | 4,068 | -34% | 1 | 1 | 0% | 953 | 1,155 | +21% | 0 | 0 | — |
case-22 | fail→pass | 12,869 | 5,909 | -54% | 1 | 1 | 0% | 2,305 | 1,494 | -35% | 0 | 0 | — |
case-23 | fail→fail | 6,472 | 4,442 | -31% | 1 | 1 | 0% | 1,100 | 1,287 | +17% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 23 cases were attempted, and 21 counted toward the lift figure. The other 2 produced results that are not comparable between the two arms, so they are excluded from the headline rather than averaged into it. The headline lift of +74 percentage points is the difference between those two pass rates over the 21 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.