{"record":{"id":"2ed1c60146f9f2e2","repo":"zeroclaw-labs/zeroclaw","slug":"sop-sop-name-not-started-reason","errorCode":null,"errorMessage":"SOP '{sop_name}' not started: {reason}","messagePattern":"SOP '(.+?)' not started: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"warning","filePath":"crates/zeroclaw-runtime/src/sop/engine.rs","lineNumber":1581,"sourceCode":"        }\n    }\n\n    /// Start a new SOP run. Returns the first action to take.\n    /// Deterministic SOPs are automatically routed to `start_deterministic_run`.\n    /// Enforce the SOP's admission policy at a start entrypoint. `Admit` proceeds;\n    /// any other outcome declines the start with a descriptive error so a trigger is\n    /// never run past its policy. dispatch pre-consults `evaluate_admission` and only\n    /// reaches a start path on `Admit`, so re-checking here (under the same held lock)\n    /// is idempotent; a DIRECT caller (`sop_execute`, or `start_deterministic_run`)\n    /// would otherwise bypass Hold / Coalesce / the `max_pending_approvals` pool.\n    fn enforce_admission(&self, sop_name: &str) -> Result<()> {\n        match self.evaluate_admission(sop_name) {\n            SopAdmission::Admit => Ok(()),\n            SopAdmission::Coalesce { existing_run_id } => bail!(\n                \"SOP '{sop_name}' not started: coalesced into in-flight run {existing_run_id}\"\n            ),\n            SopAdmission::Defer { reason } | SopAdmission::Drop { reason } => {\n                bail!(\"SOP '{sop_name}' not started: {reason}\")\n            }\n        }\n    }\n\n    fn rollback_failed_start(\n        &mut self,\n        run_id: &str,\n        claim: &ClaimToken,\n        err: anyhow::Error,\n    ) -> anyhow::Error {\n        if err.is::<TerminalPersistenceRetained>() {\n            return err;\n        }\n        self.active_runs.remove(run_id);\n        self.release_claim_best_effort(claim);\n        err\n    }\n","sourceCodeStart":1563,"sourceCodeEnd":1599,"githubUrl":"https://github.com/zeroclaw-labs/zeroclaw/blob/88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc/crates/zeroclaw-runtime/src/sop/engine.rs#L1563-L1599","documentation":"enforce_admission() saw Defer or Drop from evaluate_admission and bails with the embedded reason. Defer means backpressure: execution slots full (per-SOP or global), pending-approval pool full, or Hold policy with a run in flight. Drop means terminal refusal: SOP not loaded, SOP in cooldown, or Drop policy with slots full.","triggerScenarios":"Direct SOP start when evaluate_admission returns Defer (max_concurrent/max_concurrent_total reached, max_pending_approvals pool full, Hold with an active run) or Drop (SOP name not loaded, cooldown active after a prior run, admission_policy=Drop with slots full). The specific reason string is included in the message.","commonSituations":"Trigger storms saturating execution slots; approval pool full because gated runs await quorum; SOP disabled/renamed so it is not loaded; debouncing cooldown rejecting replays; Hold policy blocking concurrent manual runs.","solutions":["Read the reason suffix: 'execution slots full' / 'pending-approval pool full' / 'held' -> Defer: retry once capacity frees, or raise the corresponding limit.","'not loaded' -> check the SOP name and that the SOP file is loaded/enabled.","'in cooldown' -> wait out the cooldown window or review the SOP's cooldown setting if replays are legitimate.","For sustained load, move triggers to the dispatch API so Defer/Drop/Coalesce are handled as outcomes, not errors."],"exampleFix":"# before: SOP has max_concurrent = 1, a gated run holds the slot\nzeroclaw sop execute deploy\n# error: SOP 'deploy' not started: held (a run is already in flight)\n\n# after\nzeroclaw runs list --sop deploy   # resolve/finish the in-flight run first, then:\nzeroclaw sop execute deploy\n# or raise limits: max_concurrent = 2, max_pending_approvals = 4","handlingStrategy":"retry","validationCode":"match engine.evaluate_admission(sop_name) {\n    SopAdmission::Admit => engine.start_sop(sop_name, input),\n    SopAdmission::Coalesce { existing_run_id } => follow_run(existing_run_id),\n    SopAdmission::Defer { reason } => retry_later(reason),\n    SopAdmission::Drop { reason } => surface_terminal(reason), // not retryable as-is\n}","typeGuard":null,"tryCatchPattern":"match engine.start_sop(sop_name, input) {\n    Err(e) if e.to_string().contains(\"not started:\") => {\n        let reason = e.to_string().rsplit(\": \").next().unwrap_or(\"\");\n        if reason.contains(\"cooldown\") || reason.contains(\"not loaded\") {\n            return Err(e); // terminal: fix config/name or wait out cooldown\n        }\n        schedule_retry(Duration::from_secs(5)) // Defer backpressure: retry with backoff\n    }\n    rest => rest?,\n}","preventionTips":["Route bursty triggers through dispatch with an explicit admission policy; handle Defer/Drop as outcomes.","Alert on pending-approval pool saturation — it usually means approvers are not keeping up.","Verify SOP names against loaded SOPs before starting; keep cooldown windows shorter than your worst-case replay."],"tags":["sop","admission","backpressure","concurrency","cooldown","defer","drop"],"backgroundTag":"rate-limit-reached","analyzedSha":"88bb9c8533fc57ed7a03e36ca7c9ed2bf8336dcc","analyzedAt":"2026-08-23T01:07:41.857Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}