Skill v1.0.0
currentAutomated scan100/100version: "1.0.0" 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]
Fix Missing Schema Definitions
Overview
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.
When to Use This Skill
You're seeing:
- Type errors at step boundaries
- Undefined properties in step inputs/outputs
- Validation failures when passing data between steps
- TypeScript errors about incompatible types
- Runtime errors about unexpected data shapes
Root Cause
Steps without explicit schemas:
- Don't validate input data at runtime
- Don't provide TypeScript type inference
- May serialize/deserialize data incorrectly
- Can pass undefined or malformed data silently
Symptoms
Missing Input Schema
// WRONG: No input validationexport 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!}} );
Missing Output Schema
// WRONG: No output validationexport 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}} );
Both Schemas Missing
// WRONG: No validation at allexport const transformData = step( {name: 'transformData',// No schemas!fn: async input => {return transform( input );}} );
Solution
Always define both inputSchema and outputSchema for every step:
Complete Step Definition
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}} );
Schema Definition Best Practices
Use Descriptive Schemas
// Good: Clear, descriptive schemainputSchema: z.object( {userId: z.string().uuid(),email: z.string().email(),age: z.number().int().positive()} )
Handle Optional Fields
inputSchema: z.object( {required: z.string(),optional: z.string().optional(),withDefault: z.string().default( 'fallback' )} )
Use Schema Composition
// Define reusable schemasconst userSchema = z.object( {id: z.string(),name: z.string()} );const addressSchema = z.object( {street: z.string(),city: z.string()} );// Compose in stepinputSchema: z.object( {user: userSchema,address: addressSchema} )
Handle Arrays and Nested Objects
inputSchema: z.object( {items: z.array( z.object( {id: z.string(),quantity: z.number()} ) ),metadata: z.record( z.string() )} )
Finding Steps Without Schemas
Search your codebase:
# Find step definitionsgrep -rn "step({" src/workflows/# Look for steps without inputSchemagrep -A5 "step({" src/workflows/ | grep -B2 "fn:"# Check if schemas are presentgrep -rn "inputSchema:" src/workflows/grep -rn "outputSchema:" src/workflows/
Review each step definition to ensure both schemas are present.
Benefits of Explicit Schemas
- Runtime Validation: Catches data errors early
- Type Safety: Full TypeScript inference in step functions
- Documentation: Schemas document expected data shapes
- Serialization: Ensures proper data serialization between steps
- Error Messages: Clear validation errors when data is wrong
Common Schema Patterns
API Response Steps
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 foundfound: z.boolean()} ),fn: async input => {const user = await api.getUser( input.userId );return { user, found: user !== null };}} );
Transformation Steps
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 };}} );
Void Output Steps
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 };}} );
Verification
After adding schemas:
- TypeScript check:
npm run output:worker:buildshould pass without type errors - Runtime test:
npx output workflow run <name> --input '<input>'should validate correctly - Invalid input test: Pass invalid data and verify validation errors appear
Related Issues
- For Zod import issues, see
output-error-zod-import - For type mismatches despite schemas, verify schema matches actual data