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

candidate manifest name '{}' does not match proposal target

Error message

candidate manifest name '{}' does not match proposal target '{}'

What it means

After validate_candidate round-trip-loads the candidate SOP from a temp directory, it requires the loaded SOP's name to equal the proposal's sop_name byte-for-byte. This error means the manifest's [sop] name field and the ProposalDraft.sop_name (or the stored proposal's sop_name) disagree. The check prevents writing a directory named after one SOP whose manifest claims to be a different SOP.

Source

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

fn load_required(engine: &SopEngine, proposal_id: &str) -> Result<ProposalRecord> {
    engine
        .load_proposal(proposal_id)
        .map_err(anyhow::Error::new)?
        .ok_or_else(|| anyhow::Error::msg(format!("proposal not found: {proposal_id}")))
}

fn validate_candidate(sop_name: &str, manifest_toml: &str, procedure_markdown: &str) -> Result<()> {
    let tmp = tempfile::tempdir()?;
    let sop_dir = tmp.path().join(slugify(sop_name));
    fs::create_dir_all(&sop_dir)?;
    fs::write(sop_dir.join("SOP.toml"), manifest_toml)?;
    fs::write(sop_dir.join("SOP.md"), procedure_markdown)?;
    let sops = load_sops_from_directory(tmp.path(), super::parse_execution_mode("supervised"));
    if sops.len() != 1 {
        bail!("candidate SOP did not validate as exactly one loadable SOP");
    }
    if sops[0].name != sop_name {
        bail!(
            "candidate manifest name '{}' does not match proposal target '{}'",
            sops[0].name,
            sop_name
        );
    }
    if sops[0].steps.is_empty() {
        bail!("candidate SOP has no parsed steps");
    }
    Ok(())
}

fn scan_candidate(manifest_toml: &str, procedure_markdown: &str) -> Option<String> {
    let detector = LeakDetector::new();
    let content = format!("{manifest_toml}\n{procedure_markdown}");
    match detector.scan(&content) {
        LeakResult::Clean => None,
        LeakResult::Detected { patterns, .. } => Some(format!(
            "credential-like content detected: {}",

View on GitHub (pinned to 88bb9c8533)

Solutions

  1. Set the [sop] name field in manifest_toml to exactly the same string as the proposal's sop_name (case-sensitive)
  2. Or omit manifest_toml (pass None or whitespace) so create_proposal generates a matching manifest via default_manifest_toml(sop_name, description)
  3. For an Update proposal whose on-disk SOP.toml name drifted, fix the SOP.toml on disk first and re-capture the proposal via capture_successful_run or create_proposal

Example fix

// before
let manifest = r#"[sop]
name = "deploy-check"
..."#;
create_proposal(&engine, ProposalDraft { sop_name: "deploy verify".into(), manifest_toml: Some(manifest.into()), .. })?;

// after - manifest name must equal sop_name exactly
let manifest = r#"[sop]
name = "deploy verify"
..."#;
create_proposal(&engine, ProposalDraft { sop_name: "deploy verify".into(), manifest_toml: Some(manifest.into()), .. })?;
Defensive patterns

Strategy: validation

Validate before calling

// Before create_proposal, assert the manifest name equals the draft name:
fn manifest_name_matches(sop_name: &str, manifest_toml: &str) -> bool {
    manifest_toml
        .lines()
        .any(|l| l.trim_start_matches(' ').starts_with("name") && l.contains(&format!("\"{}\"", sop_name)))
}

Try / catch

Err(e) if e.to_string().contains("does not match proposal target") => {
    // read the expected name from the error, patch [sop].name, resubmit once
}

Prevention

When it happens

Trigger: Calling create_proposal with a custom manifest_toml whose [sop] name differs from draft.sop_name (e.g. an existing SOP's manifest reused as a template for a new SOP); or apply_proposal on a stored proposal whose manifest was edited after capture so the name no longer matches; also capture_successful_run when the on-disk SOP.toml name has drifted from the name the engine loaded the SOP under.

Common situations: Copy-paste of another SOP's SOP.toml as a template without renaming, case-only renames ("Deploy Check" vs "deploy check") since the comparison is exact, and renaming a SOP in config while its manifest still carries the old name.

Related errors


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