{"record":{"id":"f3ae3e25b5879e8a","repo":"Hmbown/CodeWhale","slug":"claimed-delayed-trigger-has-no-durable-task-binding","errorCode":null,"errorMessage":"Claimed delayed trigger has no durable task binding","messagePattern":"Claimed delayed trigger has no durable task binding","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/tui/src/automation_manager.rs","lineNumber":2421,"sourceCode":"            || (candidate.status == DelayedTriggerStatus::Pending && candidate.fire_at <= now))\n        {\n            continue;\n        }\n        let claimed = {\n            let manager = automations.lock().await;\n            match manager.with_transaction(|| {\n                let mut current = manager.get_trigger(&candidate.trigger_id)?;\n                if current.status == DelayedTriggerStatus::Dispatching {\n                    if !manager.eligible_scope(\n                        current\n                            .dispatch\n                            .as_ref()\n                            .and_then(|d| d.execution_scope.as_deref()),\n                    ) {\n                        return Ok(None);\n                    }\n                    if current.dispatch.is_none() || current.task_id.is_none() {\n                        bail!(\"Claimed delayed trigger has no durable task binding\");\n                    }\n                    return Ok(Some(current));\n                }\n                if !manager.eligible_scope(current.execution_scope.as_deref())\n                    || current.status != DelayedTriggerStatus::Pending\n                    || current.fire_at > now\n                    || current.owner_session_id.is_none()\n                {\n                    return Ok(None);\n                }\n                current.schema_version = CURRENT_TRIGGER_SCHEMA_VERSION;\n                current.status = DelayedTriggerStatus::Dispatching;\n                current.task_id = Some(crate::task_manager::TaskManager::new_task_id());\n                current.dispatch = Some(AutomationDispatch {\n                    execution_scope: current.execution_scope.clone(),\n                    request: NewTaskRequest {\n                        prompt: current.message.clone(),\n                        name: None,","sourceCodeStart":2403,"sourceCodeEnd":2439,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/automation_manager.rs#L2403-L2439","documentation":"When a delayed trigger is claimed (status transitions to claimed), the runtime requires a durable binding: the claimed record must carry both an AutomationDispatch and a task_id. This invariant check bails when either is missing, because without them the trigger can never be executed or resumed durably.","triggerScenarios":"Claiming a DelayedTrigger whose current.dispatch is None or current.task_id is None — typically a trigger row created or migrated by a code path that skipped durable task binding, then later picked up by the claim loop.","commonSituations":"Manually editing or pruning automation state files; upgrading from a schema where delayed triggers carried no task binding; a crash between trigger creation and dispatch binding leaving a half-written record.","solutions":["Delete the orphaned delayed-trigger record so the loop recreates it with a proper dispatch and task binding.","Re-create the automation/trigger through the normal API so the durable task binding is established before it becomes claimable.","Inspect the automation state file for the trigger and repair/complete its dispatch and task_id fields if you can do so safely."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"fn claimable(t: &DelayedTrigger) -> bool {\n    t.dispatch.is_some() && t.task_id.is_some()\n}","typeGuard":"fn has_durable_binding(t: &DelayedTrigger) -> Option<(&AutomationDispatch, &str)> {\n    Some((t.dispatch.as_ref()?, t.task_id.as_deref()?))\n}","tryCatchPattern":"match claim_delayed_trigger(&mut manager, now).await {\n    Ok(Some(t)) => run(t).await,\n    Ok(None) => {},\n    Err(e) if e.to_string().contains(\"no durable task binding\") => purge_orphan_trigger(),\n    Err(e) => return Err(e),\n}","preventionTips":["Always create delayed triggers through the API that binds dispatch + task_id atomically.","After crashes or migrations, scan triggers and drop any lacking a durable binding before resuming.","Never hand-edit automation state files."],"tags":["automation","scheduling","invariant"],"backgroundTag":"internal-invariant-violation","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}