zeroclaw-labs/zeroclaw · error · anyhow::Error
SOP rejected: {}
Error message
SOP rejected: {} What it means
save_sop clones the SOP, normalizes step numbers, then runs validate_sop_strict; if validation reports blocking findings they are joined with '; ' into this error and nothing is written to disk. Blocking findings include an empty SOP name, a step with an empty title, duplicate step numbers, and malformed step bindings or step-reference diagnostics.
Source
Thrown at crates/zeroclaw-runtime/src/sop/mod.rs:978
}
for bullet in render_step_bullets(step) {
out.push_str(&format!(" - {bullet}\n"));
}
}
out
}
/// Persist an SOP to `<sops_dir>/<name>/` as `SOP.toml` + `SOP.md`.
/// Normalizes step numbers first, then rejects the write entirely if
/// strict validation finds blocking problems; nothing touches disk on
/// failure.
pub fn save_sop(sops_dir: &Path, sop: &Sop) -> Result<()> {
let mut sop = sop.clone();
normalize_step_numbers(&mut sop);
let sop = &sop;
let validation = validate_sop_strict(sop);
if !validation.is_ok() {
anyhow::bail!("SOP rejected: {}", validation.blocking.join("; "));
}
let sop_dir = resolve_sop_dir(sops_dir, &sop.name)?;
std::fs::create_dir_all(&sop_dir)?;
let manifest = SopManifest::from_sop(sop);
let toml_content = toml::to_string_pretty(&manifest)?;
std::fs::write(sop_dir.join("SOP.toml"), toml_content)?;
std::fs::write(sop_dir.join("SOP.md"), render_steps(&sop.steps))?;
Ok(())
}
// ── Validation ──────────────────────────────────────────────────
/// Validate a loaded SOP and return a list of warnings.
pub fn validate_sop(sop: &Sop) -> Vec<String> {
let mut warnings = Vec::new();View on GitHub (pinned to 88bb9c8533)
Solutions
- Read the message: every blocking finding is listed; fix each one (titles non-empty, unique step numbers, well-formed bindings).
- Run validate_sop_strict(&sop) yourself before save to get findings without attempting a write.
- Dry-run save into a temp dir when validating authored content in tests or importers.
- Re-check numbering after hand edits, or let normalize_step_numbers handle sequencing by avoiding explicit conflicting numbers.
Example fix
// before: save fails with a joined list of blocking findings
save_sop(&sops_dir, &sop)?;
// after: validate first, surface each finding, only then save
let validation = validate_sop_strict(&sop);
if !validation.is_ok() {
for issue in &validation.blocking {
eprintln!("blocking: {issue}");
}
anyhow::bail!("SOP authoring failed validation");
}
save_sop(&sops_dir, &sop)?; Defensive patterns
Strategy: validation
Validate before calling
let validation = validate_sop_strict(&sop);
if !validation.is_ok() {
for issue in &validation.blocking {
eprintln!("blocking: {issue}");
}
anyhow::bail!("SOP failed strict validation; nothing written");
}
save_sop(&sops_dir, &sop)?; Try / catch
match save_sop(&sops_dir, &sop) {
Err(e) if e.to_string().starts_with("SOP rejected:") => {
// parse the '; '-joined findings and surface each authoring error
}
other => other?,
} Prevention
- Run validate_sop_strict in authoring tooling and CI before save.
- Dry-run save_sop into a temp dir when importing SOP packs.
- Give every step a non-empty title and unique number before submitting.
When it happens
Trigger: Saving an SOP whose steps have empty titles, duplicate step numbers that normalization cannot disambiguate, malformed binding syntax, or broken step references; the bail happens after normalize_step_numbers but before any directory is created, so no partial SOP lands on disk.
Common situations: Hand-edited SOP.md files with repeated '1.' list markers; generated SOPs where a template field was left blank; refactoring steps and renumbering by hand; bindings referencing steps by an invalid form.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- capability '{}' requires authored `with` configuration to be
- wait capability duration exceeds {MAX_WAIT_MS}ms
- invalid SOP name '{name}': must be a single path component (
- candidate SOP did not validate as exactly one loadable SOP
- candidate manifest name '{}' does not match proposal target
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/3d2a6b655828129c.
Report an issue: GitHub.