tinyhumansai/openhuman · error

workflow fan-out: expected {expected_outcomes} result(s), go

Error message

workflow fan-out: expected {expected_outcomes} result(s), got {}

What it means

Defensive invariant check after a workflow phase fan-out: the number of collected worker outcomes must equal expected_outcomes (the number of agents the phase submitted, after any max_children cap). Under FailurePolicy::CollectAll the executor should always return exactly one outcome per input; a mismatch means results were dropped or duplicated by the executor — an internal bug, not a caller error.

Source

Thrown at src/openhuman/agent/orchestration/workflow_runs/engine.rs:728

                return Ok(PhaseExecOutcome::Terminated);
            }
            Err(err) => return Err(anyhow!("workflow fan-out failed: {err}")),
        };

        let mut outcomes = Vec::with_capacity(expected_outcomes);
        for item in outcome.outcomes {
            match item.result {
                Ok(value) => outcomes.push(value),
                Err(err) => {
                    return Err(anyhow!(
                        "workflow fan-out: worker {} failed: {err}",
                        item.index
                    ));
                }
            }
        }
        if outcomes.len() != expected_outcomes {
            return Err(anyhow!(
                "workflow fan-out: expected {expected_outcomes} result(s), got {}",
                outcomes.len()
            ));
        }

        // Aggregate worker outcomes in phase order: record every spawned
        // child id, collect completed outputs, and surface the first failure.
        for outcome in outcomes {
            if let Some(oid) = outcome.orchestration_id {
                spawned_this_phase += 1;
                child_run_ids.push(oid);
            }
            match outcome.output {
                Some(out) => phase_outputs.push(out),
                None => {
                    if phase_failed.is_none() {
                        phase_failed = outcome.error;
                    }

View on GitHub (pinned to a221052e0d)

Solutions

  1. Retry the run via resume_workflow_run to rule out a one-off aggregation glitch
  2. Capture expected vs got counts plus engine/executor versions and report as a bug against the workflow engine or tinyagents
  3. Pin matching engine/executor versions until fixed
Defensive patterns

Strategy: try-catch

Try / catch

match execute_phase(/* ... */).await {
    Err(e) if e.to_string().contains("expected") && e.to_string().contains("result(s), got") => {
        // executor contract violation — one retry to rule out a glitch, then report as a bug
        retry_once_or_report(&config, &run_id, e).await
    }
    other => other,
}

Prevention

When it happens

Trigger: Cannot be produced by any argument or configuration; it fires only if the map_reduce executor violates its one-outcome-per-input contract (count mismatch after all Ok/Err results are folded).

Common situations: Executor version drift after upgrading the parallel runner; latent engine bug exposed by cancellation racing the CollectAll aggregation; effectively unseen in normal operation.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@a221052e0d (2026-08-16). Data as JSON: /api/errors/6ae103a92c0160d1. Report an issue: GitHub.