@output.ai/cli 0.4.2 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -8
- package/dist/assets/docker/docker-compose-dev.yml +9 -1
- package/dist/services/coding_agents.js +180 -8
- package/dist/services/coding_agents.spec.js +54 -11
- package/dist/templates/agent_instructions/AGENTS.md.template +20 -14
- package/dist/templates/agent_instructions/agents/{context_fetcher.md.template → workflow_context_fetcher.md.template} +1 -1
- package/dist/templates/agent_instructions/agents/workflow_debugger.md.template +98 -0
- package/dist/templates/agent_instructions/agents/workflow_planner.md.template +3 -3
- package/dist/templates/agent_instructions/agents/{prompt_writer.md.template → workflow_prompt_writer.md.template} +1 -1
- package/dist/templates/agent_instructions/agents/workflow_quality.md.template +2 -2
- package/dist/templates/agent_instructions/commands/build_workflow.md.template +2 -2
- package/dist/templates/agent_instructions/commands/debug_workflow.md.template +198 -0
- package/dist/templates/agent_instructions/commands/plan_workflow.md.template +3 -3
- package/dist/templates/agent_instructions/skills/output-error-direct-io/SKILL.md.template +249 -0
- package/dist/templates/agent_instructions/skills/output-error-http-client/SKILL.md.template +298 -0
- package/dist/templates/agent_instructions/skills/output-error-missing-schemas/SKILL.md.template +265 -0
- package/dist/templates/agent_instructions/skills/output-error-nondeterminism/SKILL.md.template +252 -0
- package/dist/templates/agent_instructions/skills/output-error-try-catch/SKILL.md.template +226 -0
- package/dist/templates/agent_instructions/skills/output-error-zod-import/SKILL.md.template +209 -0
- package/dist/templates/agent_instructions/skills/output-services-check/SKILL.md.template +128 -0
- package/dist/templates/agent_instructions/skills/output-workflow-list/SKILL.md.template +117 -0
- package/dist/templates/agent_instructions/skills/output-workflow-result/SKILL.md.template +199 -0
- package/dist/templates/agent_instructions/skills/output-workflow-run/SKILL.md.template +228 -0
- package/dist/templates/agent_instructions/skills/output-workflow-runs-list/SKILL.md.template +141 -0
- package/dist/templates/agent_instructions/skills/output-workflow-start/SKILL.md.template +201 -0
- package/dist/templates/agent_instructions/skills/output-workflow-status/SKILL.md.template +151 -0
- package/dist/templates/agent_instructions/skills/output-workflow-stop/SKILL.md.template +164 -0
- package/dist/templates/agent_instructions/skills/output-workflow-trace/SKILL.md.template +134 -0
- package/dist/templates/project/README.md.template +2 -2
- package/dist/templates/project/package.json.template +3 -2
- package/package.json +1 -1
package/dist/templates/agent_instructions/skills/output-error-nondeterminism/SKILL.md.template
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: output-error-nondeterminism
|
|
3
|
+
description: Fix non-determinism errors in Output SDK workflows. Use when seeing replay failures, inconsistent results between runs, "non-deterministic" error messages, or workflows behaving differently on retry.
|
|
4
|
+
allowed-tools: [Bash, Read]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Fix Non-Determinism Errors
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
This skill helps diagnose and fix non-determinism errors in Output SDK workflows. Workflows must be deterministic because Temporal may replay them during recovery or retries, and the replay must produce identical results.
|
|
12
|
+
|
|
13
|
+
## When to Use This Skill
|
|
14
|
+
|
|
15
|
+
You're seeing:
|
|
16
|
+
- "non-deterministic" error messages
|
|
17
|
+
- Replay failures after workflow restart
|
|
18
|
+
- Inconsistent results between runs with same input
|
|
19
|
+
- Errors during workflow recovery
|
|
20
|
+
- Warnings about determinism violations
|
|
21
|
+
|
|
22
|
+
## Root Cause
|
|
23
|
+
|
|
24
|
+
Temporal workflows must be deterministic: given the same input, they must always execute the same sequence of operations. This is because Temporal replays workflow history to recover state after crashes or restarts.
|
|
25
|
+
|
|
26
|
+
Non-deterministic operations break this replay mechanism because they produce different values each time.
|
|
27
|
+
|
|
28
|
+
## Common Causes and Solutions
|
|
29
|
+
|
|
30
|
+
### 1. Math.random()
|
|
31
|
+
|
|
32
|
+
**Problem**: Random values differ on each execution.
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// WRONG: Non-deterministic
|
|
36
|
+
export default workflow({
|
|
37
|
+
fn: async (input) => {
|
|
38
|
+
const id = Math.random().toString(36); // Different each time!
|
|
39
|
+
return await processWithId({ id });
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Solution**: Pass random values as workflow input or generate in a step.
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// Option 1: Pass as input
|
|
48
|
+
export default workflow({
|
|
49
|
+
inputSchema: z.object({
|
|
50
|
+
id: z.string() // Generate ID before calling workflow
|
|
51
|
+
}),
|
|
52
|
+
fn: async (input) => {
|
|
53
|
+
return await processWithId({ id: input.id });
|
|
54
|
+
}
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
// Option 2: Generate in a step (steps can be non-deterministic)
|
|
58
|
+
export const generateId = step({
|
|
59
|
+
name: 'generateId',
|
|
60
|
+
fn: async () => ({ id: Math.random().toString(36) })
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
export default workflow({
|
|
64
|
+
fn: async (input) => {
|
|
65
|
+
const { id } = await generateId({});
|
|
66
|
+
return await processWithId({ id });
|
|
67
|
+
}
|
|
68
|
+
});
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 2. Date.now() / new Date()
|
|
72
|
+
|
|
73
|
+
**Problem**: Timestamps change between executions.
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
// WRONG: Non-deterministic
|
|
77
|
+
export default workflow({
|
|
78
|
+
fn: async (input) => {
|
|
79
|
+
const timestamp = Date.now(); // Different each replay!
|
|
80
|
+
return await logEvent({ timestamp });
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
**Solution**: Pass timestamps as input or use Temporal's time API.
|
|
86
|
+
|
|
87
|
+
```typescript
|
|
88
|
+
// Option 1: Pass as input
|
|
89
|
+
export default workflow({
|
|
90
|
+
inputSchema: z.object({
|
|
91
|
+
timestamp: z.number()
|
|
92
|
+
}),
|
|
93
|
+
fn: async (input) => {
|
|
94
|
+
return await logEvent({ timestamp: input.timestamp });
|
|
95
|
+
}
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
// Option 2: Generate in a step
|
|
99
|
+
export const getTimestamp = step({
|
|
100
|
+
name: 'getTimestamp',
|
|
101
|
+
fn: async () => ({ timestamp: Date.now() })
|
|
102
|
+
});
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
### 3. crypto.randomUUID()
|
|
106
|
+
|
|
107
|
+
**Problem**: UUIDs differ each execution.
|
|
108
|
+
|
|
109
|
+
```typescript
|
|
110
|
+
// WRONG: Non-deterministic
|
|
111
|
+
import { randomUUID } from 'crypto';
|
|
112
|
+
|
|
113
|
+
export default workflow({
|
|
114
|
+
fn: async (input) => {
|
|
115
|
+
const requestId = randomUUID(); // Different each time!
|
|
116
|
+
return await makeRequest({ requestId });
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
**Solution**: Generate UUIDs as input or in steps.
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// Correct: Generate in step
|
|
125
|
+
export const generateRequestId = step({
|
|
126
|
+
name: 'generateRequestId',
|
|
127
|
+
fn: async () => {
|
|
128
|
+
const { randomUUID } = await import('crypto');
|
|
129
|
+
return { requestId: randomUUID() };
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
```
|
|
133
|
+
|
|
134
|
+
### 4. Dynamic Imports
|
|
135
|
+
|
|
136
|
+
**Problem**: Dynamic imports may resolve differently.
|
|
137
|
+
|
|
138
|
+
```typescript
|
|
139
|
+
// WRONG: Non-deterministic import timing
|
|
140
|
+
export default workflow({
|
|
141
|
+
fn: async (input) => {
|
|
142
|
+
const module = await import(`./handlers/${input.type}`);
|
|
143
|
+
return module.handle(input);
|
|
144
|
+
}
|
|
145
|
+
});
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
**Solution**: Use static imports and conditional logic.
|
|
149
|
+
|
|
150
|
+
```typescript
|
|
151
|
+
// Correct: Static imports with conditional use
|
|
152
|
+
import { handleTypeA } from './handlers/typeA';
|
|
153
|
+
import { handleTypeB } from './handlers/typeB';
|
|
154
|
+
|
|
155
|
+
export default workflow({
|
|
156
|
+
fn: async (input) => {
|
|
157
|
+
if (input.type === 'A') {
|
|
158
|
+
return await handleTypeA(input);
|
|
159
|
+
} else {
|
|
160
|
+
return await handleTypeB(input);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
### 5. Environment Variables
|
|
167
|
+
|
|
168
|
+
**Problem**: Environment may differ between replays.
|
|
169
|
+
|
|
170
|
+
```typescript
|
|
171
|
+
// WRONG: Environment can change
|
|
172
|
+
export default workflow({
|
|
173
|
+
fn: async (input) => {
|
|
174
|
+
const apiUrl = process.env.API_URL; // May differ on different workers
|
|
175
|
+
return await callApi({ url: apiUrl });
|
|
176
|
+
}
|
|
177
|
+
});
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
**Solution**: Pass configuration as input or use constants.
|
|
181
|
+
|
|
182
|
+
```typescript
|
|
183
|
+
// Correct: Pass as input
|
|
184
|
+
export default workflow({
|
|
185
|
+
inputSchema: z.object({
|
|
186
|
+
apiUrl: z.string()
|
|
187
|
+
}),
|
|
188
|
+
fn: async (input) => {
|
|
189
|
+
return await callApi({ url: input.apiUrl });
|
|
190
|
+
}
|
|
191
|
+
});
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
## How to Find Non-Deterministic Code
|
|
195
|
+
|
|
196
|
+
### Search for Common Patterns
|
|
197
|
+
|
|
198
|
+
```bash
|
|
199
|
+
# Find Math.random usage
|
|
200
|
+
grep -rn "Math.random" src/workflows/
|
|
201
|
+
|
|
202
|
+
# Find Date.now or new Date
|
|
203
|
+
grep -rn "Date.now\|new Date" src/workflows/
|
|
204
|
+
|
|
205
|
+
# Find crypto random functions
|
|
206
|
+
grep -rn "randomUUID\|randomBytes" src/workflows/
|
|
207
|
+
|
|
208
|
+
# Find dynamic imports
|
|
209
|
+
grep -rn "import(" src/workflows/
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Review Workflow Files
|
|
213
|
+
|
|
214
|
+
Look at your workflow `fn` functions specifically. Non-deterministic code is only a problem **in workflow functions**, not in step functions.
|
|
215
|
+
|
|
216
|
+
## Verification Steps
|
|
217
|
+
|
|
218
|
+
1. **Fix the code** using solutions above
|
|
219
|
+
2. **Run the workflow**: `npx output workflow run <name> '<input>'`
|
|
220
|
+
3. **Run again with same input**: Result should be identical
|
|
221
|
+
4. **Check for errors**: No "non-deterministic" messages
|
|
222
|
+
|
|
223
|
+
## The Determinism Rule
|
|
224
|
+
|
|
225
|
+
**Workflow functions must be deterministic:**
|
|
226
|
+
- Same input = same execution path
|
|
227
|
+
- No side effects (network, filesystem, random values)
|
|
228
|
+
- Only orchestration logic and step calls
|
|
229
|
+
|
|
230
|
+
**Step functions can be non-deterministic:**
|
|
231
|
+
- Steps record their results in Temporal history
|
|
232
|
+
- Replays use recorded results, not re-execution
|
|
233
|
+
- All I/O should happen in steps
|
|
234
|
+
|
|
235
|
+
## Debugging Tip
|
|
236
|
+
|
|
237
|
+
If unsure whether code is causing issues:
|
|
238
|
+
|
|
239
|
+
```bash
|
|
240
|
+
# Run the workflow
|
|
241
|
+
npx output workflow start my-workflow '{"input": "test"}'
|
|
242
|
+
|
|
243
|
+
# Get the workflow ID and run debug to see replay behavior
|
|
244
|
+
npx output workflow debug <workflowId> --format json
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Look for errors or warnings about non-determinism in the trace.
|
|
248
|
+
|
|
249
|
+
## Related Issues
|
|
250
|
+
|
|
251
|
+
- For I/O in workflow code, see `output-error-direct-io`
|
|
252
|
+
- For random values needed in logic, generate them in steps or pass as input
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: output-error-try-catch
|
|
3
|
+
description: Fix try-catch anti-pattern in Output SDK workflows. Use when retries aren't working, errors are being swallowed, seeing unexpected FatalError wrapping, or when step failures don't trigger retry policies.
|
|
4
|
+
allowed-tools: [Bash, Read]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Fix Try-Catch Anti-Pattern
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
This skill helps diagnose and fix a common anti-pattern where step calls are wrapped in try-catch blocks. This prevents Output SDK's retry mechanism from working properly and can lead to confusing error behavior.
|
|
12
|
+
|
|
13
|
+
## When to Use This Skill
|
|
14
|
+
|
|
15
|
+
You're seeing:
|
|
16
|
+
- Retries not working as expected
|
|
17
|
+
- Errors being swallowed silently
|
|
18
|
+
- Unexpected FatalError wrapping
|
|
19
|
+
- Step failures not triggering retry policies
|
|
20
|
+
- Errors being caught and re-thrown incorrectly
|
|
21
|
+
|
|
22
|
+
## Root Cause
|
|
23
|
+
|
|
24
|
+
When you wrap step calls in try-catch blocks, you intercept errors before the Output SDK retry mechanism can handle them. This defeats the built-in retry logic and can cause:
|
|
25
|
+
|
|
26
|
+
1. **Retries not happening**: The error is caught, so the framework doesn't know to retry
|
|
27
|
+
2. **Wrong error classification**: Re-throwing as FatalError prevents retries entirely
|
|
28
|
+
3. **Lost error context**: Original error details may be lost in the catch block
|
|
29
|
+
|
|
30
|
+
## Symptoms
|
|
31
|
+
|
|
32
|
+
### Pattern 1: Errors Swallowed
|
|
33
|
+
|
|
34
|
+
```typescript
|
|
35
|
+
// WRONG: Error is silently ignored
|
|
36
|
+
try {
|
|
37
|
+
const result = await myStep(input);
|
|
38
|
+
} catch (error) {
|
|
39
|
+
console.log('Step failed'); // Swallowed!
|
|
40
|
+
return { success: false };
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Pattern 2: FatalError Wrapping
|
|
45
|
+
|
|
46
|
+
```typescript
|
|
47
|
+
// WRONG: Turns retryable errors into fatal errors
|
|
48
|
+
try {
|
|
49
|
+
const result = await myStep(input);
|
|
50
|
+
} catch (error) {
|
|
51
|
+
throw new FatalError(error.message); // Prevents retries!
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
### Pattern 3: Re-throwing Generic Errors
|
|
56
|
+
|
|
57
|
+
```typescript
|
|
58
|
+
// WRONG: Loses error context and may affect retry behavior
|
|
59
|
+
try {
|
|
60
|
+
const result = await myStep(input);
|
|
61
|
+
} catch (error) {
|
|
62
|
+
throw new Error(`Step failed: ${error.message}`);
|
|
63
|
+
}
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Solution
|
|
67
|
+
|
|
68
|
+
**Let failures propagate naturally.** Remove try-catch blocks around step calls and let the Output SDK handle errors:
|
|
69
|
+
|
|
70
|
+
### Before (Wrong)
|
|
71
|
+
|
|
72
|
+
```typescript
|
|
73
|
+
export default workflow({
|
|
74
|
+
fn: async (input) => {
|
|
75
|
+
try {
|
|
76
|
+
const data = await fetchDataStep(input);
|
|
77
|
+
const result = await processDataStep(data);
|
|
78
|
+
return result;
|
|
79
|
+
} catch (error) {
|
|
80
|
+
throw new FatalError(error.message);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
});
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
### After (Correct)
|
|
87
|
+
|
|
88
|
+
```typescript
|
|
89
|
+
export default workflow({
|
|
90
|
+
fn: async (input) => {
|
|
91
|
+
const data = await fetchDataStep(input);
|
|
92
|
+
const result = await processDataStep(data);
|
|
93
|
+
return result;
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## When Try-Catch IS Appropriate
|
|
99
|
+
|
|
100
|
+
There are limited cases where catching errors in workflows is valid:
|
|
101
|
+
|
|
102
|
+
### 1. Optional/Fallback Steps
|
|
103
|
+
|
|
104
|
+
When a step failure should trigger an alternative path:
|
|
105
|
+
|
|
106
|
+
```typescript
|
|
107
|
+
export default workflow({
|
|
108
|
+
fn: async (input) => {
|
|
109
|
+
let data;
|
|
110
|
+
try {
|
|
111
|
+
data = await fetchFromPrimarySource(input);
|
|
112
|
+
} catch {
|
|
113
|
+
// Fallback to secondary source
|
|
114
|
+
data = await fetchFromSecondarySource(input);
|
|
115
|
+
}
|
|
116
|
+
return await processData(data);
|
|
117
|
+
}
|
|
118
|
+
});
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
### 2. Aggregate Results with Partial Failures
|
|
122
|
+
|
|
123
|
+
When processing multiple items where some may fail:
|
|
124
|
+
|
|
125
|
+
```typescript
|
|
126
|
+
export default workflow({
|
|
127
|
+
fn: async (input) => {
|
|
128
|
+
const results = [];
|
|
129
|
+
for (const item of input.items) {
|
|
130
|
+
try {
|
|
131
|
+
const result = await processItem(item);
|
|
132
|
+
results.push({ item, result, success: true });
|
|
133
|
+
} catch (error) {
|
|
134
|
+
results.push({ item, error: error.message, success: false });
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
return results; // Contains both successes and failures
|
|
138
|
+
}
|
|
139
|
+
});
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
**Note**: Even in these cases, be careful not to swallow errors that should cause the whole workflow to fail.
|
|
143
|
+
|
|
144
|
+
## Finding Try-Catch Around Steps
|
|
145
|
+
|
|
146
|
+
Search for the pattern:
|
|
147
|
+
|
|
148
|
+
```bash
|
|
149
|
+
# Find try blocks in workflow files
|
|
150
|
+
grep -rn "try {" src/workflows/
|
|
151
|
+
|
|
152
|
+
# Look for FatalError usage
|
|
153
|
+
grep -rn "FatalError" src/workflows/
|
|
154
|
+
```
|
|
155
|
+
|
|
156
|
+
Then review each match to see if it's wrapping step calls.
|
|
157
|
+
|
|
158
|
+
## How Retries Work
|
|
159
|
+
|
|
160
|
+
When you DON'T catch errors:
|
|
161
|
+
|
|
162
|
+
1. Step throws an error
|
|
163
|
+
2. Output SDK receives the error
|
|
164
|
+
3. SDK checks retry policy (configured per step)
|
|
165
|
+
4. If retries remain, step is re-executed
|
|
166
|
+
5. If retries exhausted, workflow fails with full error context
|
|
167
|
+
|
|
168
|
+
When you DO catch errors:
|
|
169
|
+
|
|
170
|
+
1. Step throws an error
|
|
171
|
+
2. Your catch block handles it
|
|
172
|
+
3. Output SDK never sees the original error
|
|
173
|
+
4. Retry logic is bypassed
|
|
174
|
+
5. You control what happens (often incorrectly)
|
|
175
|
+
|
|
176
|
+
## Configuring Retry Behavior
|
|
177
|
+
|
|
178
|
+
Instead of try-catch, configure retry policies on steps:
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
export const fetchData = step({
|
|
182
|
+
name: 'fetchData',
|
|
183
|
+
retry: {
|
|
184
|
+
maxAttempts: 3,
|
|
185
|
+
initialInterval: '1s',
|
|
186
|
+
maxInterval: '30s',
|
|
187
|
+
backoffCoefficient: 2
|
|
188
|
+
},
|
|
189
|
+
fn: async (input) => {
|
|
190
|
+
// If this fails, it will be retried according to policy
|
|
191
|
+
return await callApi(input);
|
|
192
|
+
}
|
|
193
|
+
});
|
|
194
|
+
```
|
|
195
|
+
|
|
196
|
+
## Using FatalError Correctly
|
|
197
|
+
|
|
198
|
+
FatalError is for errors that should NEVER be retried:
|
|
199
|
+
|
|
200
|
+
```typescript
|
|
201
|
+
export const validateInput = step({
|
|
202
|
+
name: 'validateInput',
|
|
203
|
+
fn: async (input) => {
|
|
204
|
+
if (!input.userId) {
|
|
205
|
+
// This will never succeed on retry
|
|
206
|
+
throw new FatalError('userId is required');
|
|
207
|
+
}
|
|
208
|
+
return input;
|
|
209
|
+
}
|
|
210
|
+
});
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Do NOT use FatalError to wrap other errors unless you're certain they shouldn't retry.
|
|
214
|
+
|
|
215
|
+
## Verification
|
|
216
|
+
|
|
217
|
+
After removing try-catch:
|
|
218
|
+
|
|
219
|
+
1. **Test normal operation**: `npx output workflow run <name> '<valid-input>'`
|
|
220
|
+
2. **Test failure scenarios**: Use input that causes step failures
|
|
221
|
+
3. **Check retry behavior**: Look for retry attempts in `npx output workflow debug <id>`
|
|
222
|
+
|
|
223
|
+
## Related Issues
|
|
224
|
+
|
|
225
|
+
- For configuring retry policies, see step definition documentation
|
|
226
|
+
- For handling expected failures gracefully, consider using conditional logic instead of try-catch
|
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: output-error-zod-import
|
|
3
|
+
description: Fix Zod schema import issues in Output SDK workflows. Use when seeing "incompatible schema" errors, type errors at step boundaries, schema validation failures, or when schemas don't match between steps.
|
|
4
|
+
allowed-tools: [Bash, Read]
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Fix Zod Import Source Issues
|
|
8
|
+
|
|
9
|
+
## Overview
|
|
10
|
+
|
|
11
|
+
This skill helps diagnose and fix a common issue where Zod schemas are imported from the wrong source. Output SDK requires schemas to be imported from `@output.ai/core`, not directly from `zod`.
|
|
12
|
+
|
|
13
|
+
## When to Use This Skill
|
|
14
|
+
|
|
15
|
+
You're seeing:
|
|
16
|
+
- "incompatible schema" errors
|
|
17
|
+
- Type errors at step boundaries
|
|
18
|
+
- Schema validation failures when passing data between steps
|
|
19
|
+
- Errors mentioning Zod types not matching
|
|
20
|
+
- "Expected ZodObject but received..." errors
|
|
21
|
+
|
|
22
|
+
## Root Cause
|
|
23
|
+
|
|
24
|
+
The issue occurs when you import `z` from `zod` instead of `@output.ai/core`. While both provide Zod schemas, they create different schema instances that aren't compatible with each other within the Output SDK context.
|
|
25
|
+
|
|
26
|
+
**Why this matters**: Output SDK uses a specific version of Zod internally for serialization and validation. When you use a different Zod instance, the schemas are technically different objects even if they define the same shape.
|
|
27
|
+
|
|
28
|
+
## Symptoms
|
|
29
|
+
|
|
30
|
+
### Error Messages
|
|
31
|
+
|
|
32
|
+
```
|
|
33
|
+
Error: Incompatible schema types
|
|
34
|
+
Error: Schema validation failed: expected compatible Zod instance
|
|
35
|
+
TypeError: Cannot read property 'parse' of undefined
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### Code Patterns That Cause This
|
|
39
|
+
|
|
40
|
+
```typescript
|
|
41
|
+
// WRONG: Importing from 'zod' directly
|
|
42
|
+
import { z } from 'zod';
|
|
43
|
+
|
|
44
|
+
const inputSchema = z.object({
|
|
45
|
+
name: z.string(),
|
|
46
|
+
});
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
## Solution
|
|
50
|
+
|
|
51
|
+
### Step 1: Find All Zod Imports
|
|
52
|
+
|
|
53
|
+
Search your codebase for incorrect imports:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
grep -r "from 'zod'" src/
|
|
57
|
+
grep -r 'from "zod"' src/
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
### Step 2: Update Imports
|
|
61
|
+
|
|
62
|
+
Change all imports from:
|
|
63
|
+
|
|
64
|
+
```typescript
|
|
65
|
+
// Wrong
|
|
66
|
+
import { z } from 'zod';
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
To:
|
|
70
|
+
|
|
71
|
+
```typescript
|
|
72
|
+
// Correct
|
|
73
|
+
import { z } from '@output.ai/core';
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
### Step 3: Verify No Direct Zod Dependencies
|
|
77
|
+
|
|
78
|
+
Check your imports don't accidentally use zod elsewhere:
|
|
79
|
+
|
|
80
|
+
```bash
|
|
81
|
+
grep -r "import.*zod" src/
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
All matches should show `@output.ai/core`, not `zod`.
|
|
85
|
+
|
|
86
|
+
## Complete Example
|
|
87
|
+
|
|
88
|
+
### Before (Wrong)
|
|
89
|
+
|
|
90
|
+
```typescript
|
|
91
|
+
// src/workflows/my-workflow/steps/process.ts
|
|
92
|
+
import { z } from 'zod'; // Wrong!
|
|
93
|
+
import { step } from '@output.ai/core';
|
|
94
|
+
|
|
95
|
+
export const processStep = step({
|
|
96
|
+
name: 'processData',
|
|
97
|
+
inputSchema: z.object({
|
|
98
|
+
id: z.string(),
|
|
99
|
+
}),
|
|
100
|
+
outputSchema: z.object({
|
|
101
|
+
result: z.string(),
|
|
102
|
+
}),
|
|
103
|
+
fn: async (input) => {
|
|
104
|
+
return { result: `Processed ${input.id}` };
|
|
105
|
+
},
|
|
106
|
+
});
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### After (Correct)
|
|
110
|
+
|
|
111
|
+
```typescript
|
|
112
|
+
// src/workflows/my-workflow/steps/process.ts
|
|
113
|
+
import { z, step } from '@output.ai/core'; // Correct!
|
|
114
|
+
|
|
115
|
+
export const processStep = step({
|
|
116
|
+
name: 'processData',
|
|
117
|
+
inputSchema: z.object({
|
|
118
|
+
id: z.string(),
|
|
119
|
+
}),
|
|
120
|
+
outputSchema: z.object({
|
|
121
|
+
result: z.string(),
|
|
122
|
+
}),
|
|
123
|
+
fn: async (input) => {
|
|
124
|
+
return { result: `Processed ${input.id}` };
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Verification Steps
|
|
130
|
+
|
|
131
|
+
### 1. Check for remaining wrong imports
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
# Should return no results
|
|
135
|
+
grep -r "from 'zod'" src/
|
|
136
|
+
grep -r 'from "zod"' src/
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
### 2. Build the project
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
npm run build
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### 3. Run the workflow
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
npx output workflow run <workflowName> '<input>'
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## Prevention
|
|
152
|
+
|
|
153
|
+
### ESLint Rule (if using ESLint)
|
|
154
|
+
|
|
155
|
+
Add a rule to prevent direct zod imports:
|
|
156
|
+
|
|
157
|
+
```javascript
|
|
158
|
+
// .eslintrc.js
|
|
159
|
+
module.exports = {
|
|
160
|
+
rules: {
|
|
161
|
+
'no-restricted-imports': ['error', {
|
|
162
|
+
paths: [{
|
|
163
|
+
name: 'zod',
|
|
164
|
+
message: "Import { z } from '@output.ai/core' instead of 'zod'"
|
|
165
|
+
}]
|
|
166
|
+
}]
|
|
167
|
+
}
|
|
168
|
+
};
|
|
169
|
+
```
|
|
170
|
+
|
|
171
|
+
### IDE Settings
|
|
172
|
+
|
|
173
|
+
Configure your editor to auto-import from `@output.ai/core`:
|
|
174
|
+
|
|
175
|
+
For VS Code, add to settings.json:
|
|
176
|
+
```json
|
|
177
|
+
{
|
|
178
|
+
"typescript.preferences.autoImportFileExcludePatterns": ["zod"]
|
|
179
|
+
}
|
|
180
|
+
```
|
|
181
|
+
|
|
182
|
+
## Common Gotchas
|
|
183
|
+
|
|
184
|
+
### Mixed Imports in Same File
|
|
185
|
+
Even one wrong import can cause issues:
|
|
186
|
+
```typescript
|
|
187
|
+
import { z } from '@output.ai/core';
|
|
188
|
+
import { z as zod } from 'zod'; // This causes problems!
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
### Indirect Dependencies
|
|
192
|
+
If a utility file uses the wrong import and is shared:
|
|
193
|
+
```typescript
|
|
194
|
+
// utils/schemas.ts
|
|
195
|
+
import { z } from 'zod'; // Wrong! This affects all files using these schemas
|
|
196
|
+
export const idSchema = z.string().uuid();
|
|
197
|
+
```
|
|
198
|
+
|
|
199
|
+
### Third-Party Libraries
|
|
200
|
+
If using external Zod schemas, you may need to recreate them:
|
|
201
|
+
```typescript
|
|
202
|
+
// Don't use: externalLibrary.schema
|
|
203
|
+
// Instead: recreate the schema with @output.ai/core's z
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
## Related Issues
|
|
207
|
+
|
|
208
|
+
- If schemas are correct but you still see type errors, check `output-error-missing-schemas`
|
|
209
|
+
- For validation failures with correct imports, verify schema definitions match actual data
|