From outputai
Fixes direct I/O errors in Output SDK workflow functions causing hangs, undefined returns, determinism violations, or failed HTTP/DB ops. Moves operations to steps.
npx claudepluginhub growthxai/output --plugin outputaiThis skill is limited to using the following tools:
This skill helps diagnose and fix a critical error pattern where I/O operations (HTTP calls, database queries, file operations) are performed directly in workflow functions instead of in steps. This violates Temporal's determinism requirements.
Fixes non-determinism errors in Output SDK workflows from Math.random(), Date.now(), or crypto.randomUUID(). Use for replay failures, inconsistent runs, or determinism violations.
Implements durable multi-step workflows on Cloudflare Workers with retries, state persistence, sleeps, event waiting, and NonRetryableError handling. Use for long-running tasks.
Creates durable, resumable workflows using Vercel's Workflow SDK. Use for surviving restarts, pausing on external events, retrying failures, or coordinating multi-step operations over time.
Share bugs, ideas, or general feedback.
This skill helps diagnose and fix a critical error pattern where I/O operations (HTTP calls, database queries, file operations) are performed directly in workflow functions instead of in steps. This violates Temporal's determinism requirements.
You're seeing:
Workflow functions must be deterministic - they should only orchestrate steps, not perform I/O directly. When you make HTTP calls, database queries, or any external operations directly in a workflow function:
// WRONG: I/O directly in workflow
export default workflow({
fn: async (input) => {
const response = await fetch('https://api.example.com/data'); // BAD!
const data = await response.json();
return { data };
}
});
// WRONG: Database I/O in workflow
export default workflow({
fn: async (input) => {
const user = await db.users.findById(input.userId); // BAD!
return { user };
}
});
// WRONG: File I/O in workflow
import fs from 'fs/promises';
export default workflow({
fn: async (input) => {
const data = await fs.readFile(input.path, 'utf-8'); // BAD!
return { data };
}
});
Move ALL I/O operations to step functions. Steps are designed to handle non-deterministic operations.
export default workflow({
fn: async (input) => {
const response = await fetch('https://api.example.com/data');
const data = await response.json();
return { data };
}
});
import { z, step, workflow } from '@outputai/core';
import { httpClient } from '@outputai/http';
// Create a step for the I/O operation
export const fetchData = step({
name: 'fetchData',
inputSchema: z.object({
endpoint: z.string(),
}),
outputSchema: z.object({
data: z.unknown(),
}),
fn: async (input) => {
const client = httpClient({ prefixUrl: 'https://api.example.com' });
const data = await client.get(input.endpoint).json();
return { data };
},
});
// Workflow only orchestrates steps
export default workflow({
inputSchema: z.object({}),
outputSchema: z.object({ data: z.unknown() }),
fn: async (input) => {
const result = await fetchData({ endpoint: 'data' });
return result;
},
});
export default workflow({
fn: async (input) => {
const user = await prisma.user.findUnique({
where: { id: input.userId }
});
const orders = await prisma.order.findMany({
where: { userId: input.userId }
});
return { user, orders };
}
});
import { z, step, workflow } from '@outputai/core';
import { prisma } from '../lib/db';
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(),
}),
fn: async (input) => {
const user = await prisma.user.findUnique({
where: { id: input.userId }
});
return { user };
},
});
export const fetchOrders = step({
name: 'fetchOrders',
inputSchema: z.object({ userId: z.string() }),
outputSchema: z.object({
orders: z.array(z.object({
id: z.string(),
total: z.number(),
})),
}),
fn: async (input) => {
const orders = await prisma.order.findMany({
where: { userId: input.userId }
});
return { orders };
},
});
export default workflow({
inputSchema: z.object({ userId: z.string() }),
outputSchema: z.object({
user: z.unknown(),
orders: z.array(z.unknown()),
}),
fn: async (input) => {
const { user } = await fetchUser({ userId: input.userId });
const { orders } = await fetchOrders({ userId: input.userId });
return { user, orders };
},
});
Search for common I/O patterns in workflow files:
# Find fetch calls
grep -rn "await fetch" src/workflows/
# Find axios calls
grep -rn "axios\." src/workflows/
# Find database operations
grep -rn "prisma\.\|db\.\|mongoose\." src/workflows/
# Find file system operations
grep -rn "fs\.\|readFile\|writeFile" src/workflows/
Then review each match to see if it's in a workflow function vs a step function.
Workflow functions should contain:
await myStep(input)Workflow functions should NOT contain:
After moving I/O to steps:
npx output workflow run <name> '<input>'npx output workflow debug <id> --format jsonoutput-error-http-clientoutput-error-nondeterminism