{"record":{"id":"ceb08d2516f345a0","repo":"Hmbown/CodeWhale","slug":"automation-run-has-no-durable-task-binding","errorCode":null,"errorMessage":"Automation run has no durable task binding","messagePattern":"Automation run has no durable task binding","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/automation_manager.rs","lineNumber":2131,"sourceCode":"            .recover_task_admission(dispatch.request.clone(), task_id.to_owned())\n            .await?\n    };\n    crate::task_manager::validate_bound_task_request(&task, &dispatch.request)?;\n    dispatch.accepted = true;\n    dispatch.suppress_report = dispatch.delivery_mode == AutomationDeliveryMode::Watcher\n        && task.status == TaskStatus::Completed\n        && task\n            .result_summary\n            .as_deref()\n            .is_some_and(|summary| summary.trim() == AUTOMATION_WATCHER_NO_REPORT_SENTINEL);\n    Ok(task)\n}\n\n/// Caller owns dispatch.lock and has already persisted this exact binding.\nasync fn enqueue_run_task(run: &mut AutomationRunRecord, tasks: &SharedTaskManager) {\n    let result = match (&mut run.dispatch, &run.task_id) {\n        (Some(dispatch), Some(task_id)) => dispatch_bound_task(dispatch, task_id, tasks).await,\n        _ => Err(anyhow::anyhow!(\n            \"Automation run has no durable task binding\"\n        )),\n    };\n    match result {\n        Ok(task) => {\n            run.error = None;\n            apply_task_status(run, &task);\n        }\n        Err(error) => {\n            // Keep the same pending identity after uncertain admission. A later\n            // tick first looks for its canonical task; no new id is allocated.\n            run.error = Some(format!(\n                \"Automation task admission needs recovery: {error:#}\"\n            ));\n        }\n    }\n}\n","sourceCodeStart":2113,"sourceCodeEnd":2149,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/433685b2024e7bc4c99e1e2e326bcad39b4d9d65/crates/tui/src/automation_manager.rs#L2113-L2149","documentation":"This error is thrown by `enqueue_run_task` in the automation manager when an automation run record lacks a durable task binding. Dispatching a run requires BOTH a dispatch handle (`run.dispatch`) and a persisted task id (`run.task_id`); if either is `None`, the caller's contract (bind first, then enqueue) was violated, so the run cannot be scheduled onto the task manager.","triggerScenarios":"Calling `enqueue_run_task` with an `AutomationRunRecord` whose `dispatch` or `task_id` field is `None` — i.e. the run was never bound to a task, or the binding was cleared/lost before enqueueing.","commonSituations":"Restoring automation state from disk where the task binding failed to persist; a run created but the task registration step failed silently; clearing a task (e.g. after cancellation) and then attempting to re-dispatch the run.","solutions":["Ensure the run is bound before enqueueing: create the task via the task manager, persist the binding, and only then call the code path that enqueues the run","Check for code paths that set `run.task_id = None` (task removal/cleanup) that later retry the run","Inspect the persisted run record to confirm the binding survived the restart/reload","Log `run.dispatch.is_some()` and `run.task_id` at the call site to identify which half of the binding is missing"],"exampleFix":"// before\nmatch result {\n    Ok(task) => { ... }\n}\n// after\nif run.dispatch.is_none() || run.task_id.is_none() {\n    // bind first: create + persist the task, then re-enter enqueue_run_task\n    bind_run_task(run, tasks).await?;\n}\nlet result = enqueue_run_task(run, tasks).await;","handlingStrategy":"validation","validationCode":"fn is_dispatchable(run: &AutomationRunRecord) -> bool {\n    run.dispatch.is_some() && run.task_id.is_some()\n}\nif !is_dispatchable(&run) { return Err(anyhow!(\"run not bound\")); }","typeGuard":"fn bound(run: &AutomationRunRecord) -> Option<(&Dispatch, &TaskId)> {\n    Some((run.dispatch.as_ref()?, run.task_id.as_ref()?))\n}","tryCatchPattern":null,"preventionTips":["Always create and persist the task binding before enqueueing a run","Model the binding in the type system (e.g. a `BoundRun` newtype) so unbound runs can't reach enqueue","Audit cleanup paths that clear `task_id` or `dispatch`","Log binding state when restoring runs from persistence"],"tags":["automation","rust","state","persistence"],"backgroundTag":"invalid-state-transition","analyzedSha":"433685b2024e7bc4c99e1e2e326bcad39b4d9d65","analyzedAt":"2026-09-15T12:24:24.634Z","contentChangedAt":"2026-09-15T12:24:24.634Z","schemaVersion":2},"datasetVersion":"2026-09-22T01:17:13.364Z"}