{"record":{"id":"5767b8fb224b4a94","repo":"zeroclaw-labs/zeroclaw","slug":"failed-step-output-is-not-captured-into-procedural","errorCode":null,"errorMessage":"failed step output is not captured into procedural memory","messagePattern":"failed step output is not captured into procedural memory","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/sop/procedural_memory.rs","lineNumber":104,"sourceCode":"    engine: &SopEngine,\n    run_id: &str,\n    requested_by: Option<String>,\n) -> Result<ProposalRecord> {\n    let run = engine\n        .get_run(run_id)\n        .ok_or_else(|| anyhow::Error::msg(format!(\"SOP run not found: {run_id}\")))?;\n    if run.status != SopRunStatus::Completed {\n        bail!(\"only completed SOP runs can be captured\");\n    }\n    if run.step_results.is_empty() {\n        bail!(\"completed run has no step results to distill\");\n    }\n    if run\n        .step_results\n        .iter()\n        .any(|step| matches!(step.status, super::types::SopStepStatus::Failed))\n    {\n        bail!(\"failed step output is not captured into procedural memory\");\n    }\n\n    let sop = engine\n        .get_sop(&run.sop_name)\n        .ok_or_else(|| anyhow::Error::msg(format!(\"SOP not loaded: {}\", run.sop_name)))?;\n    let manifest_toml = read_or_default_manifest(sop)?;\n    let procedure_markdown = append_captured_notes(sop, run_id, &run.step_results)?;\n    create_proposal(\n        engine,\n        ProposalDraft {\n            sop_name: sop.name.clone(),\n            description: sop.description.clone(),\n            manifest_toml: Some(manifest_toml),\n            procedure_markdown,\n            source_run_id: Some(run_id.to_string()),\n            requested_by,\n        },\n    )","sourceCodeStart":86,"sourceCodeEnd":122,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/sop/procedural_memory.rs#L86-L122","documentation":"capture_successful_run refuses to distill any run in which at least one step result has status SopStepStatus::Failed. Procedural memory only records fully green procedures; capturing a run with a failed step would enshrine a broken sequence (or its partial outputs) as a reusable SOP.","triggerScenarios":"Calling capture on a Completed run whose history includes a step that failed (e.g. a step failed, was retried or skipped past, and the run later completed). Any single Failed entry in run.step_results trips the guard.","commonSituations":"Runs where a failed step was bypassed by an operator approve/deny and execution continued; flaky steps that failed once then passed on a later manual run; attempts to capture 'mostly worked' runs.","solutions":["Re-run the SOP from scratch and only capture runs where every step succeeded.","Fix the failing step (its inputs, tool, or environment) before capture.","Pre-check run.step_results for any Failed status and skip capture instead of erroring."],"exampleFix":"// before: capture a run that had a failed step patched over by approval\nlet p = capture_successful_run(&engine, run_id, None).await?; // bails\n\n// after: only capture all-green runs\nlet run = engine.get_run(run_id).context(\"run missing\")?;\nlet all_green = run.step_results.iter()\n    .all(|s| s.status != SopStepStatus::Failed);\nif run.status == SopRunStatus::Completed && all_green {\n    let p = capture_successful_run(&engine, run_id, None).await?;\n}","handlingStrategy":"validation","validationCode":"let Some(run) = engine.get_run(run_id) else {\n    anyhow::bail!(\"SOP run not found: {run_id}\");\n};\nanyhow::ensure!(\n    run.step_results\n        .iter()\n        .all(|s| s.status != SopStepStatus::Failed),\n    \"run contains failed steps; re-run before capture\"\n);","typeGuard":"fn is_all_green(run: &SopRun) -> bool {\n    run.step_results\n        .iter()\n        .all(|s| s.status != SopStepStatus::Failed)\n}","tryCatchPattern":"match capture_successful_run(&engine, run_id, None).await {\n    Err(e) if e.to_string().contains(\"failed step output\") => {\n        // fix the failing step and re-run the SOP end to end before capturing\n    }\n    other => other?,\n}","preventionTips":["Only capture runs where every step succeeded.","Do not bypass failed steps with approvals if you intend to capture the run.","Pre-filter with get_run before calling capture."],"tags":["procedural-memory","capture","failed-steps"],"backgroundTag":"invalid-state-transition","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}