{"record":{"id":"a402a654a9668a64","repo":"Hmbown/CodeWhale","slug":"failed-to-start-turn-engine-operation-channel-clo","errorCode":null,"errorMessage":"Failed to start turn: engine operation channel closed","messagePattern":"Failed to start turn: engine operation channel closed","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":10204,"sourceCode":"            auto_approve,\n            translation_enabled: false,\n            allowed_tools,\n            dynamic_tools: req.dynamic_tools,\n            hook_executor: None,\n            approval_mode: policy.permission,\n            verbosity,\n            provenance: input_source.provenance(),\n        });\n\n        // Reserve mailbox capacity before claiming or persisting anything.\n        // If the caller is cancelled while capacity is unavailable, no\n        // durable or in-memory turn state has changed.\n        let permit = engine\n            .tx_op\n            .clone()\n            .reserve_owned()\n            .await\n            .map_err(|_| anyhow!(\"Failed to start turn: engine operation channel closed\"))?;\n\n        let acceptance_rx = {\n            // Lock order is active -> thread_mutation. Neither guard crosses\n            // an await, and spawning the owned lifecycle task is synchronous.\n            // The operation claim is an OS-backed file lock, so another\n            // Runtime process sharing this store cannot pass the replay check\n            // or persist a competing turn for the same operation key.\n            let mut active = self.active.lock().await;\n            let mut operation_claim = operation\n                .as_ref()\n                .map(|operation| {\n                    self.store.open_turn_operation_claim_lock(\n                        &operation.binding.operation_key_fingerprint,\n                    )\n                })\n                .transpose()?\n                .map(fd_lock::RwLock::new);\n            let operation_claim_guard = operation_claim","sourceCodeStart":10186,"sourceCodeEnd":10222,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L10186-L10222","documentation":"To start a turn, the runtime reserves a slot on the engine's operation channel via engine.tx_op.clone().reserve_owned().await. If the channel is closed (receiver dropped — the engine task is gone), it maps the error to 'Failed to start turn: engine operation channel closed'. This means no engine task is alive to accept the turn operation.","triggerScenarios":"Calling start_turn after the engine task for the thread has terminated (crashed, was aborted, or already received Op::Shutdown); racing shutdown while a turn is being started; engine worker death that was not cleaned out of active.engines.","commonSituations":"Tearing down the runtime while a caller still starts turns; an engine panic that dropped the op receiver; sequencing bugs where Shutdown was sent before the last turn start.","solutions":["Ensure no Op::Shutdown was sent to this engine before starting the turn","Check engine task logs for a panic/abort that closed the channel","Reinstall the engine for the thread and retry the turn start","Serialize shutdown against turn starts so starts cannot race teardown"],"exampleFix":"// before\nlet permit = engine.tx_op.clone().reserve_owned().await\n    .map_err(|_| anyhow!(\"Failed to start turn: engine operation channel closed\"))?;\n// after\nif !engine.is_alive() {\n    bail!(\"engine for {thread_id} is not running; reinstall before starting a turn\");\n}\nlet permit = engine.tx_op.clone().reserve_owned().await\n    .map_err(|_| anyhow!(\"Failed to start turn: engine operation channel closed\"))?;","handlingStrategy":"try-catch","validationCode":"if !engine_is_alive(&engine) { bail!(\"engine already shut down; cannot start turn\"); }","typeGuard":"fn engine_is_alive(engine: &EngineHandle) -> bool { !engine.tx_op.is_closed() }","tryCatchPattern":"match start_turn(req).await {\n    Err(e) if e.to_string().contains(\"operation channel closed\") => {\n        // engine task died: reinstall and retry once\n        manager.install_engine(&req.thread_id).await?;\n        start_turn(req).await\n    }\n    other => other,\n}","preventionTips":["Sequence turn starts before Op::Shutdown","Alert on engine task panics — they close the op channel","Check tx_op.is_closed() as a cheap liveness probe before starting turns"],"tags":["channel","engine","shutdown"],"backgroundTag":"broken-pipe","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"}