affaan-m/ECC · error

orchestration template

Error message

orchestration template {name} has no steps

What it means

This error is thrown when resolving an orchestration template by name: the template exists in the configuration, but its `steps` list is empty. The library refuses to instantiate a template that would produce zero steps, since an orchestration with no steps is meaningless and would silently do nothing. It is a defensive configuration sanity check performed after template lookup succeeds.

Solutions

  1. Add at least one step to the `steps` array of the named orchestration template in your config file.
  2. Check the template name in your invocation — you may be referencing a stub template instead of the fully populated one.
  3. Validate the config before use: iterate all orchestration_templates and fail fast on any with empty steps.
  4. If the template is intentionally empty, remove it from config rather than leaving an empty definition.

Example fix

# before (config)
[orchestration_templates.deploy]
description = "Deploy pipeline"
steps = []

# after
[orchestration_templates.deploy]
description = "Deploy pipeline"
steps = [
  { name = "build", agent = "builder" },
  { name = "verify", agent = "code-reviewer" }
]
Defensive patterns

Strategy: validation

Validate before calling

for (name, tpl) in &config.orchestration_templates {
    if tpl.steps.is_empty() {
        return Err(anyhow!("template '{name}' has no steps"));
    }
}

Type guard

fn has_steps(tpl: &OrchestrationTemplate) -> bool { !tpl.steps.is_empty() }

Try / catch

match resolver.resolve_template(name, &vars) {
    Ok(t) => t,
    Err(e) if e.to_string().contains("has no steps") => {
        eprintln!("Template '{name}' is empty; check config");
        Default::default()
    }
    Err(e) => return Err(e),
}

Prevention

When it happens

Trigger: Calling the template-resolution code (e.g. resolving/rendering an orchestration template by name) where the named template is present in `orchestration_templates` but its `steps` field is an empty array or was never populated.

Common situations: A TOML/JSON config file declares an orchestration template section with description/project/agent fields but omits the steps list; a migration or hand-edit stripped the steps; a template was copy-pasted as a stub and never filled in; a programmatic config builder created the template struct without pushing steps.

Understand the failure class

Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.

Related errors


AI-assisted analysis of affaan-m/ECC@8321021c54 (2026-09-16). Data as JSON: /api/errors/02c0a43823eb4833. Report an issue: GitHub.

Appendix: source

Thrown at ecc2/src/config/mod.rs:403

    }

    pub fn harness_runner(&self, harness: &str) -> Option<&HarnessRunnerConfig> {
        let key = harness.trim().to_ascii_lowercase();
        self.harness_runners.get(&key)
    }

    pub fn resolve_orchestration_template(
        &self,
        name: &str,
        vars: &BTreeMap<String, String>,
    ) -> Result<ResolvedOrchestrationTemplate> {
        let template = self
            .orchestration_templates
            .get(name)
            .ok_or_else(|| anyhow::anyhow!("Unknown orchestration template: {name}"))?;

        if template.steps.is_empty() {
            anyhow::bail!("orchestration template {name} has no steps");
        }

        let description = interpolate_optional_string(template.description.as_deref(), vars)?;
        let project = interpolate_optional_string(template.project.as_deref(), vars)?;
        let task_group = interpolate_optional_string(template.task_group.as_deref(), vars)?;
        let default_agent = interpolate_optional_string(template.agent.as_deref(), vars)?;
        let default_profile = interpolate_optional_string(template.profile.as_deref(), vars)?;
        if let Some(profile_name) = default_profile.as_deref() {
            self.resolve_agent_profile(profile_name)?;
        }

        let mut steps = Vec::with_capacity(template.steps.len());
        for (index, step) in template.steps.iter().enumerate() {
            let task = interpolate_required_string(&step.task, vars).with_context(|| {
                format!(
                    "resolve task for orchestration template {name} step {}",
                    index + 1
                )

View on GitHub (pinned to 8321021c54)