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

only completed SOP runs can be captured

Error message

only completed SOP runs can be captured

What it means

capture_successful_run distills a finished SOP run into a proposal, and only a run whose status is exactly SopRunStatus::Completed qualifies. Runs that are still executing, paused at a checkpoint or gate, failed, or otherwise non-terminal are refused because their step outputs are not a complete, trusted procedure.

Source

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

        status_reason: None,
        applied_at: None,
        applied_by: None,
        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(

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Wait for the run to reach Completed (poll engine.get_run(run_id).status) before capturing.
  2. If the run failed, fix and re-run it; failed runs are never capturable (see the failed-step guard).
  3. Wire capture to the run's actual completion event rather than an upstream trigger.

Example fix

// before: capture fired from a step-finished webhook, run not terminal yet
let proposal = capture_successful_run(&engine, run_id, None).await?;

// after: wait for the terminal status first
loop {
    let run = engine.get_run(run_id).context("run vanished")?;
    if run.status == SopRunStatus::Completed { break; }
    tokio::time::sleep(std::time::Duration::from_millis(500)).await;
}
let proposal = 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}");
};
if run.status != SopRunStatus::Completed {
    anyhow::bail!("run is {:?}; capture only Completed runs", run.status);
}
capture_successful_run(&engine, run_id, None).await?;

Type guard

fn is_capturable(run: &SopRun) -> bool {
    run.status == SopRunStatus::Completed && !run.step_results.is_empty()
}

Try / catch

match capture_successful_run(&engine, run_id, None).await {
    Err(e) if e.to_string().contains("only completed SOP runs") => {
        // poll status to Completed, then retry capture once
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling capture while the run is mid-flight (Running/PausedCheckpoint), after a failure, or before the final step's result has been recorded. The engine looked the run up successfully (a missing run is a different error) but found a non-Completed status.

Common situations: Automation that captures on a completion signal that fires early; capturing from a webhook that races the run's final transition; retrying capture after a failed run hoping to salvage partial output.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


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