{"record":{"id":"532a9bb177220d4a","repo":"zeroclaw-labs/zeroclaw","slug":"proposal-is-stale-target-sop-now-exists","errorCode":null,"errorMessage":"proposal {} is stale; target SOP now exists","messagePattern":"proposal (.+?) is stale; target SOP now exists","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/zeroclaw-runtime/src/sop/procedural_memory.rs","lineNumber":165,"sourceCode":"            .and_then(|sop| sop.location.clone())\n        {\n            Some(location) => contained_existing_dir(&sops_root, &location)?,\n            None => contained_sop_dir(&sops_root, &proposal.sop_name)?,\n        },\n        ProposalKind::Create => contained_sop_dir(&sops_root, &proposal.sop_name)?,\n    };\n    let current_target_hash = if target_dir.exists() {\n        Some(hash_sop_dir(&target_dir)?)\n    } else {\n        None\n    };\n    match (&proposal.kind, &proposal.target_content_hash) {\n        (ProposalKind::Create, None) if current_target_hash.is_some() => {\n            proposal.status = ProposalStatus::Stale;\n            proposal.updated_at = now_iso8601();\n            proposal.status_reason = Some(\"target SOP was created after proposal capture\".into());\n            engine.save_proposal(&proposal)?;\n            bail!(\"proposal {} is stale; target SOP now exists\", proposal.id);\n        }\n        (_, Some(expected)) if current_target_hash.as_ref() != Some(expected) => {\n            proposal.status = ProposalStatus::Stale;\n            proposal.updated_at = now_iso8601();\n            proposal.status_reason = Some(\"target SOP changed since proposal capture\".into());\n            engine.save_proposal(&proposal)?;\n            bail!(\"proposal {} is stale; inspect and re-propose\", proposal.id);\n        }\n        _ => {}\n    }\n\n    if let Some(reason) = scan_candidate(&proposal.manifest_toml, &proposal.procedure_markdown) {\n        proposal.status = ProposalStatus::Quarantined;\n        proposal.updated_at = now_iso8601();\n        proposal.status_reason = Some(reason.clone());\n        engine.save_proposal(&proposal)?;\n        bail!(\"proposal {} quarantined: {reason}\", proposal.id);\n    }","sourceCodeStart":147,"sourceCodeEnd":183,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/sop/procedural_memory.rs#L147-L183","documentation":"A Create-kind proposal (captured when the target SOP did not exist) becomes invalid if the target SOP has been created in the meantime. apply_proposal detects current_target_hash.is_some() for a Create proposal, persists status Stale with status_reason 'target SOP was created after proposal capture', and bails. Applying a create over an existing SOP would clobber content the proposal never saw.","triggerScenarios":"Capturing a proposal for a brand-new SOP, then someone authors that SOP manually (or another proposal's apply creates it) before this proposal is applied.","commonSituations":"Two proposals for the same new SOP name; a reviewer creating the SOP by hand while a captured proposal waits for approval; name collisions between teams.","solutions":["Re-capture a fresh proposal against the now-existing SOP so it becomes an update carrying the current content hash.","If the existing SOP is wrong and the proposal should win, delete the target SOP and re-propose (never blind-apply).","Reduce the window between propose and apply, and check engine.get_sop(&proposal.sop_name) before applying a Create."],"exampleFix":"// before: apply a Create proposal after the target was authored manually\napply_proposal(&engine, install_root, id, None).await?; // target appeared -> Stale\n\n// after: pre-check the target and refresh the proposal\nif engine.get_sop(&proposal.sop_name).is_some() {\n    // target now exists: capture an update proposal from the current SOP instead\n    let fresh = capture_successful_run(&engine, &run_id, None).await?;\n    apply_proposal(&engine, install_root, &fresh.id, None).await?;\n} else {\n    apply_proposal(&engine, install_root, id, None).await?;\n}","handlingStrategy":"validation","validationCode":"let proposal = engine\n    .load_proposal(proposal_id)?\n    .ok_or_else(|| anyhow::anyhow!(\"proposal not found\"))?;\nif matches!(proposal.kind, ProposalKind::Create) && engine.get_sop(&proposal.sop_name).is_some() {\n    anyhow::bail!(\"target SOP '{}' now exists; re-propose as an update\", proposal.sop_name);\n}\napply_proposal(&engine, install_root, proposal_id, None).await?;","typeGuard":null,"tryCatchPattern":"match apply_proposal(&engine, install_root, id, None).await {\n    Err(e) if e.to_string().contains(\"target SOP now exists\") => {\n        // engine persisted the proposal as Stale; capture a fresh update proposal\n    }\n    other => other?,\n}","preventionTips":["Check target existence before applying Create proposals.","Apply proposals promptly after capture to shrink the race window.","Coordinate teams so only one source creates a given SOP name."],"tags":["procedural-memory","proposal","stale","conflict"],"backgroundTag":"optimistic-concurrency-conflict","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}