windmill-labs/windmill · error · Error
taskFlow("${path}") can only be called inside a workflow()
Error message
taskFlow("${path}") can only be called inside a workflow() What it means
taskFlow(path) mirrors taskScript but composes sub-flow steps; it likewise requires an active workflow() context to register the step via ctx._nextStep. Without that context the wrapper throws this error naming the sub-flow path (typescript-client/client.ts:2292), since a flow task outside workflow() cannot be scheduled.
Source
Thrown at typescript-client/client.ts:2292
/**
* Create a task that dispatches to a separate Windmill flow.
*
* @example
* const pipeline = taskFlow("f/etl/pipeline");
* // inside workflow: await pipeline({ input: data })
*/
export function taskFlow(path: string, options?: TaskOptions): (...args: any[]) => PromiseLike<any> {
const name = path.split("/").pop() || path;
const wrapper = function (...args: any[]) {
const ctx: WorkflowCtx | null = _workflowCtx ?? Reflect.get(globalThis, "__wmill_wf_ctx");
if (ctx) {
const kwargs = args.length === 1 && typeof args[0] === "object" && args[0] !== null
? args[0]
: args.reduce((acc, v, i) => { acc[`arg${i}`] = v; return acc; }, {} as Record<string, any>);
return ctx._nextStep(name, path, kwargs, "flow", options);
}
throw new Error(`taskFlow("${path}") can only be called inside a workflow()`);
};
Object.defineProperty(wrapper, "name", { value: name });
(wrapper as any)._is_task = true;
(wrapper as any)._task_path = path;
return wrapper;
}
/**
* Mark an async function as a workflow-as-code entry point.
*
* The function must be **deterministic**: given the same inputs it must call
* tasks in the same order on every replay. Branching on task results is fine
* (results are replayed from checkpoint), but branching on external state
* (current time, random values, external API calls) must use `step()` to
* checkpoint the value so replays see the same result.
*/
export function workflow<T>(fn: (...args: any[]) => Promise<T>) {
(fn as any)._is_workflow = true;View on GitHub (pinned to e474e8803c)
Solutions
- Invoke the taskFlow function inside the workflow() callback of the enclosing workflow.
- Register sub-flows as steps in the correct nesting order — do not fire-and-forget them.
- Use a workflow test harness that installs a stub context for unit tests.
- Ensure no await between workflow() entry and the task call detaches the call from the active context.
Example fix
// before
const result = await mySubFlow({ x: 1 }); // outside workflow() -> throws
// after
await workflow(async () => {
const result = await mySubFlow({ x: 1 }); // registered as a step
}); Defensive patterns
Strategy: try-catch
Validate before calling
const inWorkflow = () => Boolean(_workflowCtx ?? Reflect.get(globalThis, '__wmill_wf_ctx'));
Type guard
function requireWorkflowCtx(): WorkflowCtx {
const ctx: WorkflowCtx | null = _workflowCtx ?? Reflect.get(globalThis, '__wmill_wf_ctx');
if (!ctx) throw new Error('taskFlow called outside workflow()');
return ctx;
} Try / catch
try {
const r = await mySubFlow({ x: 1 });
} catch (e) {
if (e.message.includes('can only be called inside a workflow()')) {
throw new Error('mySubFlow must be awaited inside workflow(), not top-level');
} else throw e;
} Prevention
- Invoke sub-flow tasks inside the enclosing workflow() callback only.
- Register sub-flows as awaited steps; never fire-and-forget them.
- Install a stub context in tests via the harness or setWorkflowCtx.
- Do not await unrelated promises between workflow() entry and the task call.
When it happens
Trigger: Calling a taskFlow-created function outside the workflow() callback — at top level, in a standalone main(), or in code that lost the context (detached promise, different module scope without the registered ctx).
Common situations: Nesting flows incorrectly (calling the sub-flow task directly instead of inside workflow()); unit-testing the task without a workflow harness; context lost after awaiting an unrelated promise that broke synchronous registration order.
Related errors
- taskScript("${path}") can only be called inside a workflow()
- waitForApproval can only be called inside a workflow()
- No workflow() entrypoint found. Wrap your main function with
- You can't use this function in a standalone script or flow s
- WAC step key "${options.key}" is already used in this workfl
AI-assisted analysis of windmill-labs/windmill@e474e8803c (2026-09-03).
Data as JSON: /api/errors/74abea90bb71e0e8.
Report an issue: GitHub.