zeroclaw-labs/zeroclaw · error · anyhow::Error

completed run has no step results to distill

Error message

completed run has no step results to distill

What it means

A defensive guard in capture_successful_run: the run reached Completed but its step_results slice is empty, so there is nothing to distill into a procedure. Completed runs are expected to carry at least one step result; an empty set means the SOP effectively executed zero recorded steps (or the run record is degenerate).

Source

Thrown at crates/zeroclaw-runtime/src/sop/procedural_memory.rs:97

        rollback_path: None,
    };
    engine.save_proposal(&proposal)?;
    Ok(proposal)
}

pub fn capture_successful_run(
    engine: &SopEngine,
    run_id: &str,
    requested_by: Option<String>,
) -> Result<ProposalRecord> {
    let run = engine
        .get_run(run_id)
        .ok_or_else(|| anyhow::Error::msg(format!("SOP run not found: {run_id}")))?;
    if run.status != SopRunStatus::Completed {
        bail!("only completed SOP runs can be captured");
    }
    if run.step_results.is_empty() {
        bail!("completed run has no step results to distill");
    }
    if run
        .step_results
        .iter()
        .any(|step| matches!(step.status, super::types::SopStepStatus::Failed))
    {
        bail!("failed step output is not captured into procedural memory");
    }

    let sop = engine
        .get_sop(&run.sop_name)
        .ok_or_else(|| anyhow::Error::msg(format!("SOP not loaded: {}", run.sop_name)))?;
    let manifest_toml = read_or_default_manifest(sop)?;
    let procedure_markdown = append_captured_notes(sop, run_id, &run.step_results)?;
    create_proposal(
        engine,
        ProposalDraft {
            sop_name: sop.name.clone(),

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Verify the SOP actually has steps before running and capturing (validate authoring with validate_sop_strict).
  2. Check engine.get_run(run_id).step_results before capture and skip degenerate runs.
  3. If a real run shows Completed with no results, investigate the engine's step recording for that run.

Example fix

// before: capture any completed run
let p = capture_successful_run(&engine, run_id, None).await?;

// after: require at least one recorded step
let run = engine.get_run(run_id).context("run missing")?;
if run.status == SopRunStatus::Completed && !run.step_results.is_empty() {
    let p = capture_successful_run(&engine, run_id, None).await?;
}
Defensive patterns

Strategy: validation

Validate before calling

let Some(run) = engine.get_run(run_id) else {
    anyhow::bail!("SOP run not found: {run_id}");
};
anyhow::ensure!(
    run.status == SopRunStatus::Completed && !run.step_results.is_empty(),
    "run has nothing to distill"
);

Type guard

fn has_distillable_steps(run: &SopRun) -> bool {
    !run.step_results.is_empty()
}

Try / catch

match capture_successful_run(&engine, run_id, None).await {
    Err(e) if e.to_string().contains("no step results to distill") => {
        // degenerate run: verify the SOP has steps, then re-run it
    }
    other => other?,
}

Prevention

When it happens

Trigger: Capturing a run of a zero-step or placeholder SOP that 'completes' without executing any steps; a run record reconstructed or trimmed in a way that dropped step results.

Common situations: Authoring scaffolding SOPs with no steps and running them as a smoke test; test fixtures that mark a run Completed manually without step results.

Related errors


AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23). Data as JSON: /api/errors/e1524104633175d7. Report an issue: GitHub.