{"record":{"id":"4d453ebf84453b3d","repo":"Hmbown/CodeWhale","slug":"task-execution-ownership-changed-refusing-a-stale-write","errorCode":null,"errorMessage":"Task execution ownership changed; refusing a stale write","messagePattern":"Task execution ownership changed; refusing a stale write","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/task_manager.rs","lineNumber":2996,"sourceCode":"        for (id, events) in &state.pending_events {\n            let task = state\n                .tasks\n                .get_mut(id)\n                .context(\"Pending task disappeared\")?;\n            self.require_execution_owner(task)?;\n            for event in events {\n                self.apply_event_to_task(task, event.clone())?;\n            }\n        }\n        Ok(())\n    }\n\n    fn require_execution_owner(&self, task: &TaskRecord) -> Result<()> {\n        if task.execution_scope.as_deref() != Some(self.execution_scope())\n            || task.execution_generation.as_deref() != Some(&self.execution_lease.generation)\n            || task.status != TaskStatus::Running\n        {\n            bail!(\"Task execution ownership changed; refusing a stale write\");\n        }\n        Ok(())\n    }\n\n    fn persist_changed_task_locked(&self, state: &mut ManagerState, id: &str) -> Result<()> {\n        let task = state.tasks.get(id).context(\"Changed task is missing\")?;\n        self.persist_task_locked(task)?;\n        state.pending_events.remove(id);\n        Ok(())\n    }\n\n    fn recover_dead_executions_locked(&self, state: &mut ManagerState) -> Result<()> {\n        for task in state.tasks.values_mut() {\n            // Unknown legacy ownership is preserved, never guessed from the\n            // visibility owner, model spelling, or current process defaults.\n            if task.status != TaskStatus::Running {\n                continue;\n            }","sourceCodeStart":2978,"sourceCodeEnd":3014,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/task_manager.rs#L2978-L3014","documentation":"Before persisting a change to a running task, the manager verifies the task is still owned by this execution: its execution_scope matches this manager's scope, its execution_generation matches the current lease generation, and its status is Running. If any changed (e.g. another generation took over, or the task stopped), a stale write would clobber newer state, so it refuses.","triggerScenarios":"Calling persist_changed_task_locked (or any path that calls require_execution_owner) for a task whose execution_scope/execution_generation no longer matches the current lease, or whose status is no longer Running.","commonSituations":"A lease renewal lapsed and another process advanced the generation; the task was cancelled or completed concurrently; recovery reassigned the task to a different execution scope and an old writer flushes late.","solutions":["Re-acquire the execution lease (refresh generation) and reload the task before retrying the write","Drop the stale write if the task has moved on; re-read current state and re-apply the intent","Check task.status == Running and matching scope/generation before issuing updates"],"exampleFix":"// before: blind update\nmanager.update_task_output(id, output).await?; // may be refused\n// after: verify ownership first\nif let Some(t) = manager.get_task(id) {\n    if t.status == TaskStatus::Running { manager.update_task_output(id, output).await?; }\n}","handlingStrategy":"validation","validationCode":"if task.status != TaskStatus::Running\n    || task.execution_scope.as_deref() != Some(current_scope)\n    || task.execution_generation.as_deref() != Some(&current_generation) {\n    return Err(anyhow!(\"ownership lost; skip stale write\"));\n}","typeGuard":"fn owns_execution(task: &TaskRecord, scope: &str, generation: &str) -> bool {\n    task.status == TaskStatus::Running\n        && task.execution_scope.as_deref() == Some(scope)\n        && task.execution_generation.as_deref() == Some(generation)\n}","tryCatchPattern":"match manager.update_task(id, change).await {\n    Err(e) if e.to_string().contains(\"refusing a stale write\") => {\n        // reload current state and re-apply the change under fresh ownership\n        let fresh = manager.get_task(id).context(\"task gone\")?;\n        manager.update_task(&fresh.id, change).await?;\n    }\n    other => other?,\n}","preventionTips":["Refresh the execution lease before long-running writes","Re-read task state after any lease renewal or recovery","Never cache TaskRecord across generation boundaries"],"tags":["concurrency","ownership","stale-write","task-manager"],"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-22T21:17:16.096Z"}