Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Fix direct I/O in Output SDK workflow functions. Use when workflow hangs, returns undefined, shows "workflow must be deterministic" errors, or when HTTP/API calls are made directly in workflow code.
.claude/skills/growthxai-output-error-direct-io/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 134% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 46% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 33% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 29% | 0% |
| case-06 | ✗→✓ | ▲ Improved | 38% | 0% |
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:
typescript// 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 }; } } );
typescript// WRONG: Database I/O in workflow export default workflow( { fn: async input => { const user = await db.users.findById( input.userId ); // BAD! return { user }; } } );
typescript// 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.
typescriptexport default workflow( { fn: async input => { const response = await fetch( 'https://api.example.com/data' ); const data = await response.json(); return { data }; } } );
typescriptimport { z, step, workflow } from '@outputai/core'; import { createKyClient } 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 = createKyClient( { prefix: '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; } } );
typescriptexport 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 }; } } );
typescriptimport { 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:
bash# 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 '<input>'npx output workflow debug <id> --jsonoutput-error-http-clientoutput-error-nondeterminism| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | fail→pass | 6,881 | 6,575 | -4% | 1 | 1 | 0% | 1,343 | 3,142 | +134% | 0 | 0 | — |
case-21 | pass→pass | 14,332 | 5,985 | -58% | 1 | 1 | 0% | 2,431 | 2,891 | +19% | 0 | 0 | — |
case-01 | fail→pass | 9,327 | 4,809 | -48% | 1 | 1 | 0% | 1,914 | 2,787 | +46% | 0 | 0 | — |
case-02 | fail→pass | 11,082 | 5,959 | -46% | 1 | 1 | 0% | 2,301 | 3,068 | +33% | 0 | 0 | — |
case-04 | fail→pass | 9,514 | 2,726 | -71% | 1 | 1 | 0% | 1,782 | 2,292 | +29% | 0 | 0 | — |
case-05 | pass→pass | 10,642 | 4,540 | -57% | 1 | 1 | 0% | 1,890 | 2,649 | +40% | 0 | 0 | — |
case-06 | fail→pass | 11,462 | 4,876 | -57% | 1 | 1 | 0% | 1,954 | 2,698 | +38% | 0 | 0 | — |
case-07 | fail→pass | 6,152 | 1,743 | -72% | 1 | 1 | 0% | 1,157 | 1,986 | +72% | 0 | 0 | — |
case-08 | fail→pass | 13,489 | 1,976 | -85% | 1 | 1 | 0% | 2,689 | 2,066 | -23% | 0 | 0 | — |
case-09 | fail→pass | 5,818 | 3,149 | -46% | 1 | 1 | 0% | 906 | 2,192 | +142% | 0 | 0 | — |
case-10 | fail→pass | 9,374 | 2,530 | -73% | 1 | 1 | 0% | 1,784 | 2,116 | +19% | 0 | 0 | — |
case-11 | fail→fail | 10,085 | 3,420 | -66% | 1 | 1 | 0% | 1,718 | 2,216 | +29% | 0 | 0 | — |
case-12 | fail→pass | 12,333 | 3,964 | -68% | 1 | 1 | 0% | 2,273 | 2,437 | +7% | 0 | 0 | — |
case-22 | pass→pass | 8,903 | 5,184 | -42% | 1 | 1 | 0% | 1,476 | 2,549 | +73% | 0 | 0 | — |
case-13 | pass→pass | 15,725 | 6,801 | -57% | 1 | 1 | 0% | 2,553 | 2,748 | +8% | 0 | 0 | — |
case-14 | pass→pass | 10,561 | 2,858 | -73% | 1 | 1 | 0% | 1,832 | 2,239 | +22% | 0 | 0 | — |
case-15 | pass→pass | 12,109 | 1,839 | -85% | 1 | 1 | 0% | 1,924 | 2,085 | +8% | 0 | 0 | — |
case-16 | fail→pass | 12,590 | 1,863 | -85% | 1 | 1 | 0% | 2,009 | 1,931 | -4% | 0 | 0 | — |
case-17 | pass→pass | 14,345 | 6,699 | -53% | 1 | 1 | 0% | 1,987 | 2,690 | +35% | 0 | 0 | — |
case-18 | pass→pass | 13,172 | 9,018 | -32% | 1 | 1 | 0% | 2,118 | 3,138 | +48% | 0 | 0 | — |
case-19 | pass→pass | 14,723 | 8,081 | -45% | 1 | 1 | 0% | 2,045 | 2,867 | +40% | 0 | 0 | — |
case-20 | pass→pass | 10,733 | 6,455 | -40% | 1 | 1 | 0% | 2,232 | 2,935 | +31% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 22 cases were attempted. The headline lift of +50 percentage points is the difference between those two pass rates over the 22 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.