{"record":{"id":"1f6e834bdc900fb1","repo":"Hmbown/CodeWhale","slug":"a-cancelled-operation-cannot-be-edited","errorCode":null,"errorMessage":"A cancelled Operation cannot be edited.","messagePattern":"A cancelled Operation cannot be edited\\.","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/operate.rs","lineNumber":1091,"sourceCode":"            Ok(())\n        })?;\n        if let Some(op) = attached {\n            return Ok(op);\n        }\n    }\n    start_operation(\n        store,\n        workspace,\n        direction,\n        burn_usd_per_hour,\n        credentials_present,\n        lead_model,\n    )\n}\n\npub fn apply_operate_patch(op: &mut Operation, patch: &serde_json::Value) -> Result<()> {\n    if op.status == OperateStatus::Cancelled {\n        anyhow::bail!(\"A cancelled Operation cannot be edited.\");\n    }\n    if let Some(direction) = patch.get(\"direction\") {\n        let next = normalize_direction(direction.as_str().map(str::to_string).unwrap_or_default());\n        if patch.get(\"leadPlan\").is_none() && next != op.direction {\n            // A changed direction supersedes the recorded lead plan: workers\n            // must stop executing slices derived from the old direction. The\n            // operation idles `awaiting_lead_plan` until the lead re-plans\n            // (same patch may instead carry an explicit replacement plan).\n            op.lead_plan = None;\n        }\n        op.direction = next;\n    }\n    if patch.get(\"burnRate\").is_some() {\n        op.burn_rate = parse_burn_rate(patch.get(\"burnRate\"))?;\n    }\n    if let Some(plan) = patch.get(\"leadPlan\") {\n        op.lead_plan = if plan.is_null() {\n            None","sourceCodeStart":1073,"sourceCodeEnd":1109,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/operate.rs#L1073-L1109","documentation":"apply_operate_patch refuses to mutate an Operation whose status is Cancelled. Cancellation is terminal: once an Operation is cancelled its recorded fields are frozen so history stays consistent, and any patch (direction, leadPlan, etc.) is rejected.","triggerScenarios":"Calling apply_operate_patch (or an edit API that routes to it) with a JSON patch targeting an Operation with status == Cancelled.","commonSituations":"UI or automation still holds a stale handle to an operation the user cancelled; a retry/update job firing after cancellation; bulk edits iterating over operations including cancelled ones.","solutions":["Check `op.status` before editing and skip cancelled operations","Create a new Operation instead of editing the cancelled one","If the operation should not have been cancelled, restart it as a new operation rather than un-cancelling"],"exampleFix":"// before\napply_operate_patch(&mut op, &patch)?;\n// after\nif op.status != OperateStatus::Cancelled {\n    apply_operate_patch(&mut op, &patch)?;\n}","handlingStrategy":"validation","validationCode":"fn editable(op: &Operation) -> bool { op.status != OperateStatus::Cancelled }","typeGuard":null,"tryCatchPattern":"match apply_operate_patch(&mut op, &patch) { Err(e) if e.to_string().contains(\"cannot be edited\") => eprintln!(\"operation was cancelled; create a new one\"), other => other?, }","preventionTips":["Filter cancelled operations out of bulk-edit lists","Check status in the UI before enabling edit controls","Treat cancellation as terminal in automation flows"],"tags":["state","immutable","operation"],"backgroundTag":"invalid-state-transition","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T16:17:23.217Z"}