{"record":{"id":"a4e42789aac49fc8","repo":"Hmbown/CodeWhale","slug":"thread-execution-settings-changed-while-preparing-a4e427","errorCode":null,"errorMessage":"Thread execution settings changed while preparing compaction; retry","messagePattern":"Thread execution settings changed while preparing compaction; retry","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":10623,"sourceCode":"            route: Box::new(route),\n            compaction: Box::new(compaction),\n        };\n        let permit = engine.tx_op.clone().reserve_owned().await.map_err(|_| {\n            anyhow!(\"Failed to trigger compaction: engine operation channel closed\")\n        })?;\n\n        let acceptance_rx = {\n            let mut active = self.active.lock().await;\n            let Some(state) = active.engines.get_mut(thread_id) else {\n                bail!(\"Thread engine not loaded\");\n            };\n            if state.active_turn.is_some() {\n                bail!(\"Thread already has an active turn\");\n            }\n            let _thread_mutation = self.store.thread_mutation.lock();\n            let mut current_thread = self.store.load_thread(thread_id)?;\n            if !thread_execution_state_matches(&thread, &current_thread) {\n                bail!(\"Thread execution settings changed while preparing compaction; retry\");\n            }\n            let previous_active_route = (state.route_identity.clone(), state.route_model.clone());\n            state.active_turn = Some(ActiveTurnState {\n                goal_id: None,\n                turn_id: turn_id.clone(),\n                interrupt_requested: false,\n                compaction_id: Some(compaction_id),\n            });\n            state.route_identity = route_identity;\n            state.route_model = route_model;\n\n            let persistence_result = (|| -> Result<()> {\n                self.store.save_turn(&turn)?;\n                current_thread.latest_turn_id = Some(turn_id.clone());\n                current_thread.updated_at = now;\n                self.store.save_thread(&current_thread)\n            })();\n            if let Err(persistence_error) = persistence_result {","sourceCodeStart":10605,"sourceCodeEnd":10641,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L10605-L10641","documentation":"Thrown by the compaction preparation step when the persisted thread record no longer matches the in-memory thread snapshot used to start compaction (`thread_execution_state_matches` fails). Execution-relevant settings (route, model, or other execution state) changed between the caller's read and the mutation-locked re-load, so proceeding would compact under the wrong configuration.","triggerScenarios":"Calling `compact_thread` while another actor changes the thread's execution settings (model/route switch, config update, thread edit) between the initial `get_thread`/route resolution and the `store.load_thread` inside the mutation lock — the mismatch check fails.","commonSituations":"User switches model while auto-compaction is preparing; a concurrent settings update lands mid-compaction-setup; race between two UI actions (edit thread + compact) on the same thread.","solutions":["Retry compaction: re-fetch the thread with the updated settings and call compact_thread again — the message explicitly says 'retry'.","Avoid changing thread execution settings (model/route) while compaction or a turn is being prepared.","Serialize settings changes and compaction through the store's thread_mutation lock or a single settings API so they cannot interleave."],"exampleFix":"// before\nruntime.compact_thread(thread_id).await?;\n// after\nmatch runtime.compact_thread(thread_id).await {\n    Err(e) if e.to_string().contains(\"execution settings changed\") => {\n        // settings moved: re-read the thread and retry once\n        runtime.compact_thread(thread_id).await?\n    }\n    other => other?,\n}","handlingStrategy":"retry","validationCode":"// Re-read the thread right before compacting so the snapshot is fresh\nlet thread = runtime.get_thread(thread_id).await?;\nlet _engine = runtime.ensure_engine_loaded(&thread).await?;\nruntime.compact_thread(thread_id).await?;","typeGuard":"fn settings_match(snapshot: &Thread, persisted: &Thread) -> bool {\n    snapshot.execution_state() == persisted.execution_state()\n}","tryCatchPattern":"const MAX_RETRIES: usize = 2;\nfor _ in 0..MAX_RETRIES {\n    match runtime.compact_thread(thread_id).await {\n        Err(e) if e.to_string().contains(\"execution settings changed\") => continue,\n        other => break other,\n    }\n}","preventionTips":["Do not change thread model/route settings while compaction or turn preparation is in flight.","Apply execution-setting changes through a single serialized API path.","Retry compaction once on this error before surfacing it; the mismatch is usually transient."],"tags":["tui","compaction","config-change","retry","race-condition"],"backgroundTag":"conflicting-config-options","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"}