{"record":{"id":"74abea90bb71e0e8","repo":"windmill-labs/windmill","slug":"taskflow-path-can-only-be-called-inside-a-wo","errorCode":null,"errorMessage":"taskFlow(\"${path}\") can only be called inside a workflow()","messagePattern":"taskFlow\\(\"(.+?)\"\\) can only be called inside a workflow\\(\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"typescript-client/client.ts","lineNumber":2292,"sourceCode":"\n/**\n * Create a task that dispatches to a separate Windmill flow.\n *\n * @example\n * const pipeline = taskFlow(\"f/etl/pipeline\");\n * // inside workflow: await pipeline({ input: data })\n */\nexport function taskFlow(path: string, options?: TaskOptions): (...args: any[]) => PromiseLike<any> {\n  const name = path.split(\"/\").pop() || path;\n  const wrapper = function (...args: any[]) {\n    const ctx: WorkflowCtx | null = _workflowCtx ?? Reflect.get(globalThis, \"__wmill_wf_ctx\");\n    if (ctx) {\n      const kwargs = args.length === 1 && typeof args[0] === \"object\" && args[0] !== null\n        ? args[0]\n        : args.reduce((acc, v, i) => { acc[`arg${i}`] = v; return acc; }, {} as Record<string, any>);\n      return ctx._nextStep(name, path, kwargs, \"flow\", options);\n    }\n    throw new Error(`taskFlow(\"${path}\") can only be called inside a workflow()`);\n  };\n  Object.defineProperty(wrapper, \"name\", { value: name });\n  (wrapper as any)._is_task = true;\n  (wrapper as any)._task_path = path;\n  return wrapper;\n}\n\n/**\n * Mark an async function as a workflow-as-code entry point.\n *\n * The function must be **deterministic**: given the same inputs it must call\n * tasks in the same order on every replay. Branching on task results is fine\n * (results are replayed from checkpoint), but branching on external state\n * (current time, random values, external API calls) must use `step()` to\n * checkpoint the value so replays see the same result.\n */\nexport function workflow<T>(fn: (...args: any[]) => Promise<T>) {\n  (fn as any)._is_workflow = true;","sourceCodeStart":2274,"sourceCodeEnd":2310,"githubUrl":"https://github.com/windmill-labs/windmill/blob/e474e8803ce2ff5c2df09a58dab51d45f5c922ca/typescript-client/client.ts#L2274-L2310","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","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."],"exampleFix":"// before\nconst result = await mySubFlow({ x: 1 }); // outside workflow() -> throws\n// after\nawait workflow(async () => {\n  const result = await mySubFlow({ x: 1 }); // registered as a step\n});","handlingStrategy":"try-catch","validationCode":"const inWorkflow = () =>\n  Boolean(_workflowCtx ?? Reflect.get(globalThis, '__wmill_wf_ctx'));","typeGuard":"function requireWorkflowCtx(): WorkflowCtx {\n  const ctx: WorkflowCtx | null = _workflowCtx ?? Reflect.get(globalThis, '__wmill_wf_ctx');\n  if (!ctx) throw new Error('taskFlow called outside workflow()');\n  return ctx;\n}","tryCatchPattern":"try {\n  const r = await mySubFlow({ x: 1 });\n} catch (e) {\n  if (e.message.includes('can only be called inside a workflow()')) {\n    throw new Error('mySubFlow must be awaited inside workflow(), not top-level');\n  } else throw e;\n}","preventionTips":["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."],"tags":["workflow","task","context"],"backgroundTag":"function-requires-workflow-context","analyzedSha":"e474e8803ce2ff5c2df09a58dab51d45f5c922ca","analyzedAt":"2026-09-03T12:38:19.024Z","contentChangedAt":"2026-09-03T12:38:19.024Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}