ruvnet/ruflo · error
task step requires config.agentId or…
Error message
task step ${step.stepId} requires config.agentId or workflow.variables.defaultAgentId What it means
During workflow_run, every step of type 'task' resolves its executor as step.config.agentId || workflow.variables.defaultAgentId. If both are falsy the step throws before executeAgentTask is called, marking the step failed with this message. It is a workflow-definition validation error: the workflow JSON lacks the agent binding for that step.
Solutions
- Add config.agentId to the failing step (stepId is named in the message), pointing at an existing agent id.
- Or set workflow variables.defaultAgentId when creating/starting the run so all agent-less task steps inherit it.
- Validate the workflow definition before execution: every type:'task' step must have a non-empty config.agentId or the run must receive defaultAgentId.
- Check exact key names and casing — only 'agentId' (camelCase) under step.config is read.
Example fix
// before
{ steps: [{ stepId: 's1', type: 'task', name: 'summarize', config: { prompt: 'Summarize {{input}}' } }] }
// -> throws: task step s1 requires config.agentId or workflow.variables.defaultAgentId
// after
{ steps: [{ stepId: 's1', type: 'task', name: 'summarize', config: { agentId: 'summarizer-1', prompt: 'Summarize {{input}}' } }] } Defensive patterns
Strategy: validation
Validate before calling
interface WorkflowStep { stepId: string; type: string; config?: Record<string, unknown> }
function validateTaskSteps(steps: WorkflowStep[], variables: Record<string, unknown>): string[] {
const hasDefault = typeof variables.defaultAgentId === 'string' && variables.defaultAgentId;
return steps
.filter(s => s.type === 'task')
.filter(s => !s.config?.agentId && !hasDefault)
.map(s => `task step ${s.stepId} requires config.agentId or workflow.variables.defaultAgentId`);
}
const problems = validateTaskSteps(workflow.steps, variables);
if (problems.length) throw new Error(problems.join('; ')); // fail BEFORE workflow_run Type guard
function taskStepIsBound(step: { type: string; config?: Record<string, unknown> }, variables: Record<string, unknown>): boolean {
if (step.type !== 'task') return true;
return Boolean(step.config?.agentId) || Boolean(variables.defaultAgentId);
} Try / catch
try {
return await callTool('workflow_run', { workflowId, variables });
} catch (e) {
const m = /task step (\S+) requires config\.agentId/.exec(String((e as Error).message));
if (m) return configError(`Workflow step '${m[1]}' has no agent binding — set config.agentId or pass defaultAgentId.`);
throw e;
} Prevention
- Validate workflow templates at authoring time: every type:'task' step needs config.agentId or the run needs defaultAgentId.
- Use exact camelCase key 'agentId' — the lookup is case-sensitive with no aliasing.
- Smoke-test new workflow JSON with a dry one-step run before putting it in automation.
When it happens
Trigger: Running a workflow whose task step has config without agentId while the run variables also lack defaultAgentId; passing variables: {} at start; a typo like config.agentid (case-sensitive) or nesting agentId one level too deep in the step config.
Common situations: Hand-written workflow JSON/YAML templates that omit per-step agent bindings; templates designed around defaultAgentId run without setting that variable; LLM-generated workflow definitions missing required fields; renaming config keys during migration from an older workflow schema.
Understand the failure class
Background: "missing required argument" and "the following required arguments were not provided": what required-argument errors mean and how to fix them — this error's family across 20 libraries.
Related errors
- agent_execute failed
- Dangerous key segment rejected
- Dual-mode config must export a workers array
- Key exceeds maximum nesting depth of
- localCompute: no adapter for graphId=
AI-assisted analysis of ruvnet/ruflo@fa13ee4ad6 (2026-08-18).
Data as JSON: /api/errors/07b611ef28c44780.
Report an issue: GitHub.
Appendix: source
Thrown at v3/@claude-flow/cli/src/mcp-tools/workflow-tools.ts:355
workflow.status = 'failed';
saveWorkflowStore(store);
break;
}
const step = workflow.steps[i];
step.status = 'running';
step.startedAt = new Date().toISOString();
const stepStart = Date.now();
saveWorkflowStore(store);
let stepEntry: typeof stepResults[number] = { stepId: step.stepId, type: step.type, status: 'running' };
try {
if (step.type === 'task') {
const cfg = step.config as Record<string, unknown>;
const agentId = (cfg.agentId as string) || (workflow.variables.defaultAgentId as string);
const promptTpl = (cfg.prompt as string) || step.name;
if (!agentId) throw new Error(`task step ${step.stepId} requires config.agentId or workflow.variables.defaultAgentId`);
const prompt = interp(promptTpl);
const result = await executeAgentTask({
agentId,
prompt,
systemPrompt: cfg.systemPrompt ? interp(String(cfg.systemPrompt)) : undefined,
maxTokens: cfg.maxTokens as number | undefined,
temperature: cfg.temperature as number | undefined,
timeoutMs: cfg.timeoutMs as number | undefined,
});
if (!result.success) throw new Error(result.error || 'agent_execute failed');
step.result = result;
workflow.variables[`${step.stepId}.output`] = result.output;
workflow.variables.lastStepOutput = result.output;
stepEntry = { stepId: step.stepId, type: 'task', status: 'completed', durationMs: result.durationMs, output: result.output };
} else if (step.type === 'wait') {
const cfg = step.config as Record<string, unknown>;
const ms = Math.min(Math.max(0, (cfg.ms as number) || 0), 60000);
await new Promise(r => setTimeout(r, ms));View on GitHub (pinned to fa13ee4ad6)