{"record":{"id":"c0b7454d0449b71a","repo":"Hmbown/CodeWhale","slug":"config-reload-rejected-because-active-thread-route","errorCode":null,"errorMessage":"Config reload rejected because active thread routes are invalid: {}","messagePattern":"Config reload rejected because active thread routes are invalid: (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":2741,"sourceCode":"                    )\n                })\n                .collect()\n        };\n\n        let mut validated = Vec::with_capacity(entries.len());\n        let mut failures = Vec::new();\n        for (thread_id, engine, provider_identity, engine_model, active_turn_id) in entries {\n            match resolve_runtime_thread_route_for_identity(\n                &new_config,\n                &provider_identity,\n                Some(&engine_model),\n            ) {\n                Ok(route) => validated.push((thread_id, engine, route, active_turn_id)),\n                Err(err) => failures.push(format!(\"{thread_id}: {err}\")),\n            }\n        }\n        if !failures.is_empty() {\n            bail!(\n                \"Config reload rejected because active thread routes are invalid: {}\",\n                failures.join(\"; \")\n            );\n        }\n\n        // `engine_load` is still held here, so a thread cannot construct an\n        // engine from the accepted config before its process-wide read/tool\n        // byte limits are active. Rejected reloads leave the prior limits in\n        // place.\n        let workshop_activation = crate::tools::large_output_router::WorkshopConfig::install_active(\n            new_config.workshop.as_ref(),\n        );\n        let workflow_table = new_config.workflow_config();\n        {\n            let mut guard = self.config.write();\n            *guard = new_config;\n        }\n        crate::tools::workflow::set_session_workflow_config(&self.workspace, workflow_table);","sourceCodeStart":2723,"sourceCodeEnd":2759,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/0c42157ee52f9d55af2b506d71b46249910f77d3/crates/tui/src/runtime_threads.rs#L2723-L2759","documentation":"During a config reload, every active runtime thread's route (engine + provider identity + engine model) is re-resolved against the new config. If any thread would end up with an invalid route, the entire reload is rejected and the previous config stays in force; the message lists each failing thread id with its underlying route error. This keeps running turns from being stranded on a config they can no longer reconstruct.","triggerScenarios":"Triggering a config reload while active threads exist whose provider/model no longer resolves under the incoming config: a provider block removed, a model renamed or no longer available, an API key/route default changed such that resolve_runtime_thread_route_for_identity fails for that thread's provider_identity and engine_model.","commonSituations":"Editing providers or models in the config file (or via /config reload) while sessions are running; removing a provider that an old thread still pins; renaming a model that active threads reference; rotating route defaults that break a thread's stored identity.","solutions":["Read the per-thread reasons in the message; each names the thread id and the underlying route error (e.g. unknown provider, missing model).","Fix the new config so every active thread's provider identity and engine model still resolve (restore the provider entry, keep the model alias, or add a compatible route).","Alternatively, let the affected threads finish or close them, then reload the config with no active routes to validate.","Retry the reload only after the named threads resolve; the running system keeps the prior config until then, so nothing is lost by waiting."],"exampleFix":"# before\n# config.toml: provider \"openai\" removed, but thread t-42 still routes through it\ncodewhale /config reload\n# bails: Config reload rejected because active thread routes are invalid: t-42: ...\n\n# after\n# config.toml: keep or restore a resolvable entry for the active thread's identity\n[providers.openai]\napi_key = \"...\"\n\ncodewhale /config reload   # accepted; new limits installed under held engine_load","handlingStrategy":"validation","validationCode":"// Rust: dry-run route resolution for active threads before applying a config reload\nlet mut conflicts = Vec::new();\nfor (thread_id, provider_identity, engine_model) in active_thread_identities() {\n    if resolve_runtime_thread_route_for_identity(&new_config, &provider_identity, Some(&engine_model)).is_err() {\n        conflicts.push(thread_id);\n    }\n}\nanyhow::ensure!(\n    conflicts.is_empty(),\n    \"reload would strand threads {conflicts:?}; fix providers/models or close these threads first\"\n);\nreload_config(new_config)?;","typeGuard":null,"tryCatchPattern":"// Rust: the failed reload is safe (old config stays active); parse the per-thread list to report\nmatch reload_config(&new_config) {\n    Ok(()) => Ok(()),\n    Err(err) if err.to_string().contains(\"active thread routes are invalid\") => {\n        // prior config still in force; surface thread ids from err and guide the user\n        Err(anyhow::anyhow!(\"reload aborted, previous config active. Resolve routes for: {err}\"))\n    }\n    Err(err) => Err(err),\n}","preventionTips":["Before removing or renaming providers/models in config, check which active threads route through them.","Prefer additive config changes while sessions are running; do removals when no threads are active.","Keep model aliases stable so stored thread identities keep resolving across config edits.","Remember a rejected reload is atomic: the old config remains in force, so fix and retry rather than force-applying."],"tags":["config","reload","routing","threads","rust"],"backgroundTag":"invalid-config-value","analyzedSha":"0c42157ee52f9d55af2b506d71b46249910f77d3","analyzedAt":"2026-08-20T21:50:45.477Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}