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

  1. Fix the root cause that made the step fail — inspect the thrown error's `.result` (serialized original error) and `.step_key` for details.
  2. 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).
  3. Change the step name/key if the old failed checkpoint must be bypassed deliberately (creates a fresh key).
  4. 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

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


AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03). Data as JSON: /api/errors/89764fcdc14744fd. Report an issue: GitHub.