nikivdev/code · error

No lifecycle down action found. Define task 'down', set [lif

Error message

No lifecycle down action found. Define task 'down', set [lifecycle].down_task, or enable [lifecycle.domains] cleanup in {}

What it means

run_down checks that at least one teardown action exists and ran: a 'down' task, an explicit [lifecycle].down_task, or a [lifecycle.domains] cleanup. If neither the task chain nor any domain action ran, it bails pointing at the flow.toml path. Like run_up, it's a config-completeness guard.

Source

Thrown at src/lifecycle.rs:79

    if !task_ran && lifecycle.down_task.is_none() {
        processes::kill_processes(KillOpts {
            config: project.flow_path.clone(),
            task: None,
            pid: None,
            all: true,
            force: false,
            timeout: 5,
        })?;
        task_ran = true;
    }

    let mut domain_action_ran = false;
    if let Some(domains_cfg) = lifecycle.domains.as_ref() {
        domain_action_ran = run_domains_down(domains_cfg)?;
    }

    if !task_ran && !domain_action_ran {
        bail!(
            "No lifecycle down action found. Define task 'down', set [lifecycle].down_task, or enable [lifecycle.domains] cleanup in {}",
            project.flow_path.display()
        );
    }

    Ok(())
}

fn run_required_task(config_path: &Path, task_name: &str, args: Vec<String>) -> Result<bool> {
    match run_task(config_path, task_name, args) {
        Ok(()) => Ok(true),
        Err(err) if is_task_not_found(&err) => {
            bail!("lifecycle task '{}' not found", task_name);
        }
        Err(err) => Err(err),
    }
}

View on GitHub (pinned to a747e741ae)

Solutions

  1. Add a `down` task to flow.toml under [tasks]
  2. Or set [lifecycle] down_task = "teardown" pointing at an existing task
  3. Or configure [lifecycle.domains] so a domain cleanup action can run

Example fix

// before (flow.toml)
[tasks]
up = "docker compose up -d"
// after
[tasks]
up = "docker compose up -d"
down = "docker compose down"
[lifecycle]
down_task = "down"
Defensive patterns

Strategy: validation

Validate before calling

let cfg = std::fs::read_to_string("flow.toml")?;
let has_down = cfg.contains("down_task")
    || cfg.lines().any(|l| l.starts_with("down "))
    || cfg.contains("[lifecycle.domains]");
if !has_down { eprintln!("flow.toml defines no down action"); }

Try / catch

if let Err(e) = run_down(&project, opts) {
    if e.to_string().contains("No lifecycle down action found") {
        eprintln!("Add a 'down' task, [lifecycle].down_task, or [lifecycle.domains] cleanup");
    }
    return Err(e);
}

Prevention

When it happens

Trigger: Running the down lifecycle command when flow.toml has no 'down' task, no [lifecycle].down_task, and either no [lifecycle.domains] section or run_domains_down performed no action.

Common situations: Project defines up tasks but never wired teardown; down task renamed without updating down_task; [lifecycle.domains] configured but its engine produced no runnable cleanup; editing the wrong flow.toml.

Related errors


AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01). Data as JSON: /api/errors/02844ce5546ebf16. Report an issue: GitHub.