llm-output-schema-cons…
Zod schema constraints that Anthropic rejects or silently ignores when sent as structured-output tool definitions via aiSdk.Output.object(). Use when writing…
Fix missing schema definitions in Output SDK steps. Use when seeing type errors, undefined properties at step boundaries, validation failures, or when step inputs/outputs aren't being properly typed.
$ npx -y skills add growthxai/output --skill output-error-missing-schemas --agent claude-codeHow it fires
How this skill gets triggered: by you, by Claude, or both.
/output-error-missing-schemasContext preview
The summary Claude sees to decide when to auto-load this skill.
Fix missing schema definitions in Output SDK steps. Use when seeing type errors, undefined properties at step boundaries, validation failures, or when step inputs/outputs aren't being properly typed.
name: output-error-missing-schemas description: Fix missing schema definitions in Output SDK steps. Use when seeing type errors, undefined properties at step boundaries, validation failures, or when step inputs/outputs aren't being properly typed. allowed-tools: [Bash, Read]
This skill helps diagnose and fix issues caused by steps that lack explicit `inputSchema` or `outputSchema` definitions. Schemas are essential for type safety, validation, and proper data serialization between steps.
You're seeing:
Steps without explicit schemas:
// WRONG: No input validation
export const processData = step( {
name: 'processData',
// inputSchema: missing!
outputSchema: z.object( { result: z.string() } ),
fn: async input => {
return { result: input.value }; // input.value might be undefined!
}
} );// WRONG: No output validation
export const fetchData = step( {
name: 'fetchData',
inputSchema: z.object( { id: z.string() } ),
// outputSchema: missing!
fn: async input => {
return { data: await getFromApi( input.id ) }; // Output shape not validated
}
} );// WRONG: No validation at all
export const transformData = step( {
name: 'transformData',
// No schemas!
fn: async input => {
return transform( input );
}
} );Always define both `inputSchema` and `outputSchema` for every step:
import { z, step } from '@outputai/core';
export const processData = step( {
name: 'processData',
inputSchema: z.object( {
id: z.string(),
value: z.number(),
optional: z.string().optional()
} ),
outputSchema: z.object( {
result: z.string(),
processedAt: z.number()
} ),
fn: async input => {
// input is fully typed: { id: string, value: number, optional?: string }
return {
result: `Processed ${input.id}`,
processedAt: Date.now()
};
// output is validated against outputSchema
}
} );// Good: Clear, descriptive schema
inputSchema: z.object( {
userId: z.string().uuid(),
email: z.string().email(),
age: z.number().int().positive()
} )inputSchema: z.object( {
required: z.string(),
optional: z.string().optional(),
withDefault: z.string().default( 'fallback' )
} )// Define reusable schemas
const userSchema = z.object( {
id: z.string(),
name: z.string()
} );
const addressSchema = z.object( {
street: z.string(),
city: z.string()
} );
// Compose in step
inputSchema: z.object( {
user: userSchema,
address: addressSchema
} )inputSchema: z.object( {
items: z.array( z.object( {
id: z.string(),
quantity: z.number()
} ) ),
metadata: z.record( z.string() )
} )Search your codebase:
# Find step definitions
grep -rn "step({" src/workflows/
# Look for steps without inputSchema
grep -A5 "step({" src/workflows/ | grep -B2 "fn:"
# Check if schemas are present
grep -rn "inputSchema:" src/workflows/
grep -rn "outputSchema:" src/workflows/Review each step definition to ensure both schemas are present.
1. **Runtime Validation**: Catches data errors early 2. **Type Safety**: Full TypeScript inference in step functions 3. **Documentation**: Schemas document expected data shapes 4. **Serialization**: Ensures proper data serialization between steps 5. **Error Messages**: Clear validation errors when data is wrong
export const fetchUser = step( {
name: 'fetchUser',
inputSchema: z.object( {
userId: z.string()
} ),
outputSchema: z.object( {
user: z.object( {
id: z.string(),
name: z.string(),
email: z.string()
} ).nullable(), // Handle not found
found: z.boolean()
} ),
fn: async input => {
const user = await api.getUser( input.userId );
return { user, found: user !== null };
}
} );export const transformData = step( {
name: 'transformData',
inputSchema: z.object( {
raw: z.array( z.unknown() )
} ),
outputSchema: z.object( {
processed: z.array( z.object( {
id: z.string(),
value: z.number()
} ) ),
count: z.number()
} ),
fn: async input => {
const processed = input.raw.map( transformItem );
return { processed, count: processed.length };
}
} );For steps that don't return meaningful data:
export const logEvent = step( {
name: 'logEvent',
inputSchema: z.object( {
event: z.string(),
data: z.record( z.unknown() )
} ),
outputSchema: z.object( {
logged: z.literal( true )
} ),
fn: async input => {
await logger.log( input.event, input.data );
return { logged: true };
}
} );After adding schemas:
1. **TypeScript check**: `npm run output:worker:build` should pass without type errors 2. **Runtime test**: `npx output workflow run <name> --input '<input>'` should validate correctly 3. **Invalid input test**: Pass invalid data and verify validation errors appear
The open-source TypeScript framework for building AI workflows and agents. Designed for Claude Code — describe what you want, Claude builds it, with all the best practices already in place. One framework.
Repo: growthxai/output
Zod schema constraints that Anthropic rejects or silently ignores when sent as structured-output tool definitions via aiSdk.Output.object(). Use when writing…
Guide to the providerOptions structure in .prompt files — decision tree for where an option goes, common mistakes, per-provider quick reference, and Anthropic…
Implement an Output SDK workflow from a plan document. Use when the user asks to build, implement, or code a workflow from an existing plan, or after…
View and edit encrypted credentials in an Output.ai project. Use when adding secrets, updating API keys, verifying credential values, or retrieving a specific…
Wire encrypted credentials to environment variables using the credential: convention. Use when setting up LLM provider keys (ANTHROPIC_API_KEY, OPENAI_API_KEY)…