{"record":{"id":"3f18a12b54cbcb53","repo":"zeroclaw-labs/zeroclaw","slug":"sop-name-does-not-contain-a-safe-path-component","errorCode":null,"errorMessage":"SOP name does not contain a safe path component","messagePattern":"SOP name does not contain a safe path component","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/sop/procedural_memory.rs","lineNumber":380,"sourceCode":"    procedure_markdown: &str,\n) -> Result<()> {\n    fs::create_dir_all(target_dir)?;\n    atomic_write_file(&target_dir.join(\"SOP.toml\"), manifest_toml)?;\n    atomic_write_file(&target_dir.join(\"SOP.md\"), procedure_markdown)?;\n    Ok(())\n}\n\nfn atomic_write_file(path: &Path, content: &str) -> Result<()> {\n    let tmp = path.with_extension(\"tmp\");\n    fs::write(&tmp, content)?;\n    fs::rename(&tmp, path)?;\n    Ok(())\n}\n\nfn contained_sop_dir(sops_root: &Path, sop_name: &str) -> Result<PathBuf> {\n    let slug = slugify(sop_name);\n    if slug.is_empty() {\n        bail!(\"SOP name does not contain a safe path component\");\n    }\n    ensure_relative_component(&slug)?;\n    let target = sops_root.join(slug);\n    ensure_within_root(sops_root, &target)?;\n    Ok(target)\n}\n\n/// Validate that an already-existing SOP directory (taken from the loaded\n/// `Sop.location`) stays within `sops_root`, rejecting `..` and symlink escapes.\nfn contained_existing_dir(sops_root: &Path, location: &Path) -> Result<PathBuf> {\n    let target = location.to_path_buf();\n    ensure_within_root(sops_root, &target)?;\n    Ok(target)\n}\n\nfn ensure_within_root(sops_root: &Path, target: &Path) -> Result<()> {\n    let root = resolve_existing_ancestor(sops_root)?;\n    let resolved_target = resolve_existing_ancestor(target)?;","sourceCodeStart":362,"sourceCodeEnd":398,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/sop/procedural_memory.rs#L362-L398","documentation":"contained_sop_dir derives the on-disk SOP directory as sops_root.join(slugify(sop_name)). slugify strips everything that is not a safe path character; if the result is the empty string, the SOP name consisted entirely of unsafe characters and there is no directory name to write to. The bail fires before any path is joined, so nothing is written.","triggerScenarios":"apply_proposal or create_proposal with a sop_name made only of characters slugify removes - punctuation/symbols/whitespace-only strings like \"???\", \"---\", \"...\", \"   \"; or the Update path where a previously loaded SOP's stored name slugifies to empty (data corrupted or hand-edited store).","commonSituations":"Model-generated proposal drafts with placeholder names ('###', '...'), SOP stores edited by hand, or names in a non-Latin script that the slugifier does not transliterate.","solutions":["Give the SOP a name containing at least one letter or digit, e.g. \"deploy-check\"","If this fires on apply_proposal for an Update, inspect the stored proposal's sop_name and the loaded SOP's name - fix the store record or the SOP's manifest name","Add a pre-check in your proposal-creation UI: slugify(name).is_empty() means reject before calling create_proposal"],"exampleFix":"// before\ncreate_proposal(&engine, ProposalDraft { sop_name: \"???\".into(), .. })?;\n\n// after\ncreate_proposal(&engine, ProposalDraft { sop_name: \"deploy check\".into(), .. })?;","handlingStrategy":"validation","validationCode":"fn valid_sop_name(name: &str) -> bool {\n    !slugify(name).is_empty()\n}","typeGuard":"fn is_safe_sop_name(name: &str) -> bool { !slugify(name).is_empty() }","tryCatchPattern":"Err(e) if e.to_string().contains(\"does not contain a safe path component\") => {\n    // reject the draft at the UI/model boundary and ask for a word-based name\n}","preventionTips":["Require at least one alphanumeric word in SOP names at draft creation","Never auto-generate names from symbols/punctuation placeholders","Unit-test name generation against slugify for every locale you support"],"tags":["sop","slug","path-safety","validation","procedural-memory"],"backgroundTag":"invalid-slug","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}