---
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

```typescript
// 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!
  }
} );
```

### Missing Output Schema

```typescript
// 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
  }
} );
```

### Both Schemas Missing

```typescript
// WRONG: No validation at all
export 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

```typescript
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

```typescript
// Good: Clear, descriptive schema
inputSchema: z.object( {
  userId: z.string().uuid(),
  email: z.string().email(),
  age: z.number().int().positive()
} )
```

### Handle Optional Fields

```typescript
inputSchema: z.object( {
  required: z.string(),
  optional: z.string().optional(),
  withDefault: z.string().default( 'fallback' )
} )
```

### Use Schema Composition

```typescript
// 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
} )
```

### Handle Arrays and Nested Objects

```typescript
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:

```bash
# 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.

## Benefits of Explicit Schemas

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

## Common Schema Patterns

### API Response Steps

```typescript
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 };
  }
} );
```

### Transformation Steps

```typescript
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:

```typescript
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:

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

## Related Issues

- For Zod import issues, see `output-error-zod-import`
- For type mismatches despite schemas, verify schema matches actual data
