windmill-labs/windmill · error · Error
Step '${name}' failed
Error message
Step '${name}' failed What it means
In the Windmill TypeScript client, `ctx.step(name, fn)` checkpoints its result so replays of a `workflow()` run do not re-execute completed steps. If a step previously failed, the failure was stored as an error marker (`__wmill_error`) in the completed map; on replay, the context rebuilds and throws it as a `TaskError` with message "Step '<name>' failed". This is deliberate: a failed step stays failed on every round, and catch handlers get the same error shape each time.
Source
Thrown at typescript-client/client.ts:1912
}
console.log(`\n--- WAC: sleep(${key}, ${seconds}s) ---`);
this._raiseSuspend({
mode: "sleep",
key,
seconds: Math.max(1, Math.round(seconds)),
steps: [],
});
}
async _runInlineStep<T>(name: string, fn: () => T | Promise<T>): Promise<T> {
this._rethrowSwallowed();
const key = this._allocKey(name || "step");
if (key in this.completed) {
const value = this.completed[key];
if (value && typeof value === "object" && (value as any).__wmill_error) {
throw taskErrorFromMarker(value, `Step '${name}' failed`);
}
return value as T;
}
if (this._executingKey !== null) {
return new Promise(() => {});
}
console.log(`\n--- WAC: ${key} ---`);
const startedAt = new Date().toISOString();
console.log(`WM_WAC_STEP: ${JSON.stringify({ key, started_at: startedAt })}`);
const t0 = Date.now();
// A thrown step still has to reach `completed_steps`, or a replay with
// `_executingKey` set finds nothing recorded and parks forever on the
// never-resolving promise above. A nested StepSuspend is control flow,
// not a step failure.
let result: T;
let errored = false;View on GitHub (pinned to e474e8803c)
Solutions
- Fix the root cause that made the step fail — inspect the thrown error's `.result` (serialized original error) and `.step_key` for details.
- Wrap the step call in try/catch inside the workflow body and handle the failure (e.g. return a fallback or re-run with corrected inputs).
- Change the step name/key if the old failed checkpoint must be bypassed deliberately (creates a fresh key).
- Delete or fix the underlying resource the step operated on (bad credential, missing input, flaky downstream) and resume the run.
Example fix
// before: unhandled failing step kills the workflow on every replay
const data = await ctx.step('fetch', () => fetchRecords());
// after: handle failure explicitly so replay is consistent
let data;
try {
data = await ctx.step('fetch', () => fetchRecords());
} catch (e) {
data = []; // fallback; same branch taken on every replay round
} Defensive patterns
Strategy: try-catch
Validate before calling
// before calling step, check the checkpoint map (internal, debug use)
const key = name || 'step';
if (key in ctx.completed && ctx.completed[key]?.__wmill_error) {
console.warn(`step '${key}' has a stored failure; it will rethrow on replay`);
} Type guard
function isWmillErrorMarker(v: unknown): v is { __wmill_error: true; message?: string; result?: { error: unknown }; step_key?: string } {
return typeof v === 'object' && v !== null && (v as any).__wmill_error === true;
} Try / catch
try {
const value = await ctx.step('myStep', fn);
} catch (e: any) {
if (e?.name === 'TaskError') {
// inspect e.result.error for the original cause, e.step_key for the step
} else throw e;
} Prevention
- Validate step inputs before the step body runs so transient failures don't get checkpointed.
- Wrap unreliable I/O inside the step fn with bounded retries rather than letting it throw.
- Remember the workflow body re-runs from the top each round — never assume a failed step is retried silently.
- Keep step names stable and meaningful so checkpoint keys and failures map clearly to code.
When it happens
Trigger: Calling `workflow()` ctx.step(name, fn) where the step's key already exists in the completed/checkpoint map with a value carrying `__wmill_error: true` — i.e. a replay round (recovery, resume, or later round of the re-run-from-top loop) reaching a step that threw in an earlier round.
Common situations: A workflow run resumes after a crash or suspension and re-enters the body from the top; the step failed in the first round and has no successful checkpoint. Also hit when a try/catch inside the workflow body catches a step failure and continues, so subsequent rounds replay the step and the stored failure is rethrown deterministically.
Related errors
- No workflow() entrypoint found. Wrap your main function with
- Resource ${remotePath} uses '!inline_fileset ${dirPath}', bu
- file path must refer to a file.
- File already exists: + filePath
- File already exists: + filePath
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/89764fcdc14744fd.
Report an issue: GitHub.