Skip to content
flow-error-try-catch-removal logo

Remove Try-Catch Antipattern from Step Calls

flow-error-try-catch-removal

Remove try-catch antipattern from step calls during Flow to Output SDK migration. Use when converting workflow code that wraps step calls in try-catch blocks.

SKILL.md

Full skill instructions

Remove Try-Catch Antipattern from Step Calls

Overview

This skill helps identify and remove the try-catch antipattern that's common in Flow SDK code but incorrect in Output SDK. In Output SDK, step calls should NOT be wrapped in try-catch blocks - errors should propagate up the chain.

When to Use This Skill

During Migration:

  • Converting Flow SDK workflows that use try-catch around activity calls
  • Reviewing migrated workflow code for error handling patterns

Code Patterns to Fix:

  • Try-catch blocks wrapping step calls
  • Re-throwing errors as FatalError after catching
  • Generic error handling around workflow execution

Why Try-Catch is Wrong for Step Calls

In Output SDK, the workflow execution engine handles errors automatically:

  1. Automatic Retry Logic: Steps have built-in retry mechanisms that try-catch interferes with
  2. Error Propagation: Errors need to bubble up for proper workflow state management
  3. Observability: Caught and re-thrown errors lose stack trace and context
  4. Workflow Recovery: The engine can only recover workflows properly when it sees the original error

Error Patterns

Flow SDK Pattern (Common but Wrong for Output SDK)

// WRONG: Wrapping step calls in try-catch
export default workflow( {
  name: 'someWorkflow',
  description: 'a workflow description',
  inputSchema: WorkflowInputSchema,
  outputSchema: WorkflowOutputSchema,
  fn: async input => {
    try {
      // Step 1: Get User data
      const userData = await getUserData( { userId: input.userId } );

      // Step 2: Get role configuration
      const roleConfig = await getRoleConfig( { role: userData.role } );

      // Step 3: Ensure something
      await ensureSomething( { config: roleConfig } );

      return { success: true };
    } catch ( error ) {
      throw new FatalError( error instanceof Error ? error.message : 'Unknown workflow error' );
    }
  }
} );

Output SDK Pattern (Correct)

// CORRECT: Let errors propagate naturally
export default workflow( {
  name: 'someWorkflow',
  description: 'a workflow description',
  inputSchema: WorkflowInputSchema,
  outputSchema: WorkflowOutputSchema,
  fn: async input => {
    // Step 1: Get User data
    const userData = await getUserData( { userId: input.userId } );

    // Step 2: Get role configuration
    const roleConfig = await getRoleConfig( { role: userData.role } );

    // Step 3: Ensure something
    await ensureSomething( { config: roleConfig } );

    return { success: true };
  }
} );

Solution

Step 1: Find Try-Catch Blocks in Workflows

Search for try-catch patterns in workflow files:

grep -r "try {" src/​workflows/​*/​workflow.ts
grep -r "} catch" src/​workflows/​*/​workflow.ts

Step 2: Analyze the Try-Catch Purpose

Before removing, understand what the try-catch was doing:

  1. Generic error wrapping → Remove entirely
  2. Specific error transformation → Consider if really needed
  3. Cleanup logic → May need different approach

Step 3: Remove Try-Catch, Keep Logic

Remove the try-catch wrapper but keep the step calls:

// Before
fn: async input => {
  try {
    const result = await someStep( input );
    return result;
  } catch ( error ) {
    throw new FatalError( error.message );
  }
}

// After
fn: async input => {
  const result = await someStep( input );
  return result;
}

When Try-Catch IS Appropriate

Try-catch is still appropriate in specific cases:

1. Inside Steps (Not Workflows)

Steps can use try-catch for internal logic:

export const fetchExternalData = step( {
  name: 'fetchExternalData',
  inputSchema: z.object( { url: z.string() } ),
  fn: async ( input ) => {
    try {
      const response = await fetch( input.url );
      return response.json();
    } catch ( error ) {
      // Transform to ValidationError for expected failures
      throw new ValidationError( `Failed to fetch: ${input.url}` );
    }
  }
} );

2. For Specific Error Types

When you need to handle a specific error type differently:

fn: async input => {
  const userData = await getUserData( { userId: input.userId } );

  try {
    await sendNotification( { userId: userData.id } );
  } catch ( error ) {
    // Only catch specific expected errors
    if ( error instanceof NotificationDisabledError ) {
      // User has notifications disabled - continue without notification
      console.log( 'Notifications disabled for user' );
    } else {
      throw error; // Re-throw unexpected errors
    }
  }

  return { success: true };
}

3. For Optional Operations

When a failure shouldn't stop the workflow:

fn: async input => {
  const result = await processData( input );

  // Optional: try to cache result, but don't fail if caching fails
  try {
    await cacheResult( { key: input.id, value: result } );
  } catch {
    // Caching failed - log but continue
    console.warn( 'Failed to cache result' );
  }

  return result;
}

Complete Migration Example

Before (Flow SDK with Try-Catch)

import { FatalError } from '@flow/​sdk';
import { getUserData, processUser, saveResults } from './​activities';

export default class UserProcessingWorkflow {
  async execute(input: WorkflowInput): Promise<WorkflowOutput> {
    try {
      const user = await getUserData(input.userId);
      const processed = await processUser(user);
      await saveResults(processed);
      return { success: true, userId: user.id };
    } catch (error) {
      throw new FatalError(`Workflow failed: ${error.message}`);
    }
  }
}

After (Output SDK without Try-Catch)

import { workflow, z } from '@output.ai/​core';
import { getUserData, processUser, saveResults } from './​steps.js';
import { WorkflowInputSchema, WorkflowOutputSchema } from './​types.js';

export default workflow( {
  name: 'userProcessing',
  description: 'Process user data',
  inputSchema: WorkflowInputSchema,
  outputSchema: WorkflowOutputSchema,
  fn: async input => {
    const user = await getUserData( { userId: input.userId } );
    const processed = await processUser( { user } );
    await saveResults( { data: processed } );
    return { success: true, userId: user.id };
  }
} );

Verification Steps

1. Search for remaining try-catch in workflows

# Should return minimal results (only appropriate uses)
grep -A5 "try {" src/​workflows/​*/​workflow.ts

2. Run the workflow

npx output workflow run <workflowName> --input '{}'

3. Test error scenarios

Intentionally trigger an error to verify it propagates correctly.

Related Skills

  • flow-convert-workflow-definition - Full workflow conversion
  • flow-convert-activities-to-steps - Step conversion patterns
  • flow-validation-checklist - Complete migration validation

More skills from majiayu000

xiaohongshu logo
majiayu000/claude-arsenal

xiaohongshu

xiaohongshu

286 148
View
agent-task-conductor logo
majiayu000/claude-skill-registry

agent-task-conductor

Conduct multi-agent task orchestration and workflow coordination.

663 1
View
conductor-setup logo
majiayu000/claude-skill-registry

conductor-setup

Initialize project with Conductor artifacts (product definition,

663 1
View
animation-designer logo
majiayu000/claude-skill-registry

animation-designer

Expert in web animations, transitions, and motion design using Framer Motion and CSS

663 1
View
diagramming logo
majiayu000/claude-skill-registry

diagramming

Creates Mermaid and ASCII diagrams for flowcharts, architecture, ERDs, state machines, mindmaps, and more. Use when user mentions diagram, flowchart, mermaid, ASCII diagram, text diagram, terminal diagram, visualize, C4, mindmap, architecture diagram, sequence diagram, ERD, or needs visual docume...

663 1
View
h3-pg logo
majiayu000/claude-skill-registry-data

h3-pg

PostgreSQL bindings for H3 hexagonal grid system. Use when working with H3 cells in Postgres, including spatial indexing, geometry/geography integration, and raster analysis.

23 1
View
conductor-development logo
majiayu000/claude-skill-registry

conductor-development

Context-Driven Development skill for projects using Conductor. Use this skill when you detect a `conductor/` directory in the project, when working on tasks defined in a `plan.md` file, or when the user asks about tracks, specs, or plans. Automatically applies TDD workflow, tracks task completion...

663 1
View
conductor-status logo
majiayu000/claude-skill-registry

conductor-status

Display project status, active tracks, and next actions

663 1
View
dockerization logo
majiayu000/claude-skill-registry

dockerization

Official Stakpak application containerization standard operating procedure, a step-by-step guidline to properly dockerize applications. This is a rule book curated by the Stakpak Team.

663 1
View

Popular AI tools

Kaiber logo
Video

Kaiber

Generate, edit, and beat-sync AI video with leading models in one workspace.

Paid
View
Vimcal logo
Productivity

Vimcal

The world's fastest calendar for remote work

Free
View

Transform Your Design with AI Designer by ImgCreator.ai

Freemium
View
Akool AI logo
Content & writing

Akool AI

Revolutionizing Video Production with AI-Powered Creativity

Paid
View

Extend an image past the frame and let AI fill the new aspect ratio.

Freemium
View
StarByFace logo
Security

StarByFace

Discover your celebrity doppelgänger with StarByFace!

Free
View
C

ChainClarity explains 700+ crypto whitepapers in plain English, with layered summaries, comparisons, research tools, alerts, and a $4.99 Pro plan.

Freemium
View
Opus Clip logo
Coding & apps

Opus Clip

Opus.ai: Revolutionize Your Web Experience

Free
View