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
- Set the [sop] name field in manifest_toml to exactly the same string as the proposal's sop_name (case-sensitive)
- Or omit manifest_toml (pass None or whitespace) so create_proposal generates a matching manifest via default_manifest_toml(sop_name, description)
- 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
- Derive [sop].name programmatically from draft.sop_name when building manifests
- Omit manifest_toml to let default_manifest_toml produce a guaranteed-matching manifest
- Treat name and directory slug as one concept: rename SOPs by re-proposing, not by editing manifests
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
- candidate SOP did not validate as exactly one loadable SOP
- candidate SOP has no parsed steps
- SOP name does not contain a safe path component
- {field} cannot be empty
- google_workspace.allowed_operations[{i}].resource must not b
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/48e776a4d80a1726.
Report an issue: GitHub.