zeroclaw-labs/zeroclaw · error · anyhow::Error
{field} cannot be empty
Error message
{field} cannot be empty What it means
create_proposal validates three required text fields with require_nonempty, which trims and rejects blank values in a fixed order: sop_name first, then procedure_markdown, then description. The bail message names the offending field. This is the earliest validation in the proposal pipeline - it fires before leak scanning and candidate round-tripping.
Source
Thrown at crates/zeroclaw-runtime/src/sop/procedural_memory.rs:469
fn hash_sop_dir(dir: &Path) -> Result<String> {
let mut hasher = Sha256::new();
for name in ["SOP.toml", "SOP.md"] {
let path = dir.join(name);
hasher.update(name.as_bytes());
hasher.update([0]);
if path.exists() {
hasher.update(fs::read(path)?);
}
hasher.update([0]);
}
Ok(format!("sha256:{}", hex::encode(hasher.finalize())))
}
fn require_nonempty<'a>(field: &str, value: &'a str) -> Result<&'a str> {
let trimmed = value.trim();
if trimmed.is_empty() {
bail!("{field} cannot be empty");
}
Ok(trimmed)
}
fn safe_component(value: &str) -> String {
let out = value
.chars()
.map(|ch| {
if ch.is_ascii_alphanumeric() || ch == '-' || ch == '_' {
ch
} else {
'-'
}
})
.collect::<String>();
out.trim_matches('-').to_string()
}
View on GitHub (pinned to 88bb9c8533)
Solutions
- Populate all three fields with non-blank text before calling create_proposal; the reported field name tells you which one is blank
- If the error comes from capture_successful_run, fix the source SOP's blank description in its manifest/SOP.toml and reload the engine
- Trim inputs at the call site so accidental whitespace-only submissions are caught early
Example fix
// before
let draft = ProposalDraft {
sop_name: "deploy-check".into(),
description: " ".into(),
manifest_toml: None,
procedure_markdown: md,
source_run_id: None,
requested_by: None,
};
// after
let draft = ProposalDraft {
sop_name: "deploy-check".into(),
description: "Verify the deploy finished".into(),
manifest_toml: None,
procedure_markdown: md,
source_run_id: None,
requested_by: None,
}; Defensive patterns
Strategy: validation
Validate before calling
fn draft_is_complete(d: &ProposalDraft) -> bool {
[&d.sop_name, &d.procedure_markdown, &d.description]
.iter()
.all(|f| !f.trim().is_empty())
} Try / catch
Err(e) if e.to_string().contains("cannot be empty") => {
// the message names the field (sop_name | procedure_markdown | description):
// collect the missing field, prompt for it, resubmit the draft
} Prevention
- Trim-and-check all three ProposalDraft text fields at the form/model boundary
- Keep SOP descriptions mandatory in your authoring flow, since captured runs inherit them
- Write unit tests asserting create_proposal rejects each blank field by name
When it happens
Trigger: Calling sop::procedural_memory::create_proposal with a draft whose sop_name, procedure_markdown, or description is empty or whitespace-only ("", " ", "\n"). The first blank field in the order sop_name -> procedure_markdown -> description is the one reported.
Common situations: UI/model flows that build ProposalDraft from optional form fields without defaults, or capture_successful_run operating on a SOP whose stored description is empty (the SOP loader allowed it but proposals do not).
Related errors
- candidate SOP did not validate as exactly one loadable SOP
- candidate manifest name '{}' does not match proposal target
- candidate SOP has no parsed steps
- SOP name does not contain a safe path component
- wait capability duration exceeds {MAX_WAIT_MS}ms
AI-assisted analysis of zeroclaw-labs/zeroclaw@88bb9c8533 (2026-08-23).
Data as JSON: /api/errors/f434a11e58d2282a.
Report an issue: GitHub.