{"record":{"id":"04c26690c13ccde6","repo":"Hmbown/CodeWhale","slug":"unpromoted-task-stage-contains-execution-evidence-refusing","errorCode":null,"errorMessage":"Unpromoted task stage contains execution evidence; refusing to replay it","messagePattern":"Unpromoted task stage contains execution evidence; refusing to replay it","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/task_manager.rs","lineNumber":1646,"sourceCode":"    pub(crate) async fn recover_task_admission(\n        &self,\n        request: NewTaskRequest,\n        task_id: String,\n    ) -> Result<TaskRecord> {\n        validate_preallocated_task_id(&task_id)?;\n        if let Some(task) = self.read_bound_task(&task_id)? {\n            validate_bound_task_request(&task, &request)?;\n            return Ok(task);\n        }\n        let staged_path = self.tasks_dir.join(format!(\".{task_id}.json.pending\"));\n        let admission = if let Some(staged) = read_bound_task_file(&staged_path, &task_id)? {\n            validate_bound_task_request(&staged, &request)?;\n            if staged.status != TaskStatus::Queued\n                || staged.started_at.is_some()\n                || staged.thread_id.is_some()\n                || staged.turn_id.is_some()\n            {\n                bail!(\"Unpromoted task stage contains execution evidence; refusing to replay it\");\n            }\n            // The original resolved settings remain durable in this stage\n            // until promotion, including if recovery itself is interrupted.\n            self.admit_task_record(staged, true).await\n        } else {\n            self.add_task_with_id(request.clone(), task_id.clone())\n                .await\n        };\n        match admission {\n            Ok(task) => Ok(task),\n            Err(error) => {\n                // Preserve a task promoted before a torn response; do not\n                // replace its identity or convert accepted work into a retry.\n                if let Some(task) = self.read_bound_task(&task_id)? {\n                    validate_bound_task_request(&task, &request)?;\n                    Ok(task)\n                } else {\n                    Err(error)","sourceCodeStart":1628,"sourceCodeEnd":1664,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/task_manager.rs#L1628-L1664","documentation":"When replaying a staged (unpromoted) task during recovery, the manager refuses if the staged record already shows execution evidence: a status other than Queued, or a set started_at, thread_id, or turn_id. Replaying such a stage would duplicate or corrupt a task that was already (partially) executed, so the admission is aborted instead.","triggerScenarios":"Calling the recovery/promotion path (staged-task replay via admit_task_record with recover_stage=true, e.g. startup replay or a recover_staged_task-style call) on a stage whose TaskStatus is not Queued or that already has started_at/thread_id/turn_id populated.","commonSituations":"Re-running startup recovery while a worker already promoted and began the task (crash between promotion and cleanup); replaying the same stage file twice; copying a tasks directory that contains post-promotion stages.","solutions":["Check the staged record before replaying: only replay when status == Queued and started_at/thread_id/turn_id are all None","If the stage already has execution evidence, promote/adopt it through the normal task-adoption path instead of replaying it as new","Clear stale stage files that belong to tasks already promoted, so recovery does not see them","Serialize recovery with the worker's dispatch lock so a concurrently promoted task is not staged-replayed"],"exampleFix":"// before\nmgr.recover_staged_task(&task_id, &request).await?;\n// after\nif let Some(staged) = mgr.read_staged_task(&task_id)? {\n    if staged.status == TaskStatus::Queued\n        && staged.started_at.is_none()\n        && staged.thread_id.is_none()\n        && staged.turn_id.is_none()\n    {\n        mgr.recover_staged_task(&task_id, &request).await?;\n    }\n}","handlingStrategy":"validation","validationCode":"fn replayable(st: &TaskRecord) -> bool { st.status == TaskStatus::Queued && st.started_at.is_none() && st.thread_id.is_none() && st.turn_id.is_none() }","typeGuard":null,"tryCatchPattern":"match replay_result { Err(e) if e.to_string().contains(\"execution evidence\") => adopt_existing_task_instead()?, r => r?, }","preventionTips":["Run recovery under the dispatch lock so promotion and replay cannot race","Only replay stage files whose status is still Queued","Clean up promoted stage files promptly after promotion"],"tags":["rust","tasks","state-machine","recovery"],"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-23T08:17:48.524Z"}