{"record":{"id":"609bd869e589839d","repo":"Hmbown/CodeWhale","slug":"route-preference-must-be-nonempty-and-contain-no-control","errorCode":null,"errorMessage":"Route preference must be nonempty and contain no control characters","messagePattern":"Route preference must be nonempty and contain no control characters","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/route_preferences.rs","lineNumber":362,"sourceCode":"    let mut doc = raw\n        .parse::<toml_edit::DocumentMut>()\n        .map_err(|_| anyhow::anyhow!(\"Could not parse route configuration; contents omitted\"))?;\n    parse_config(raw)?;\n    persistence::migrate_legacy_route_preferences(path, &mut doc)?;\n    Ok(doc)\n}\n\n/// Edit one route selection in an already-prepared candidate without saving.\n/// Callers must prepare migration first and atomically save the final snapshot.\npub fn set_document(\n    path: &Path,\n    doc: &mut toml_edit::DocumentMut,\n    key: &str,\n    value: &str,\n) -> Result<()> {\n    ensure!(is_route_key(key), \"Not a route preference key: {key}\");\n    let value = value.trim();\n    ensure!(\n        !value.is_empty() && !value.chars().any(char::is_control),\n        \"Route preference must be nonempty and contain no control characters\"\n    );\n    if let Some(key) = project_root_key(path, key) {\n        return persistence::set_document_value(doc, &[key], value);\n    }\n    let config = parse_config(&doc.to_string())?;\n    if key == \"provider\" {\n        let identity = config\n            .resolve_provider_pin_identity(value)\n            .map_err(anyhow::Error::msg)?;\n        persistence::set_document_value(\n            doc,\n            &[\"provider\"],\n            identity.persisted_id().unwrap_or(&identity.key),\n        )?;\n        // Same root-alias authority as the Runtime/TUI provider writer: a CLI\n        // switch must not leave the incoming route holding the outgoing one's","sourceCodeStart":344,"sourceCodeEnd":380,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/route_preferences.rs#L344-L380","documentation":"After key validation, set_document trims the value and rejects values that are empty or contain control characters, since route preferences are identifiers/names written into TOML and a blank or control-bearing value would corrupt the config or silently clear the selection.","triggerScenarios":"Calling set()/set_document() with value \"\" or a value containing control chars (e.g. stray \\n, \\t, or terminal escape bytes from clipboard paste).","commonSituations":"Unset environment variables interpolated empty in shell scripts; copy-paste of model names with hidden control characters; automated edits passing blank placeholders.","solutions":["Trim the value and ensure it is nonempty before calling","Strip control characters from user input before persisting","Validate the model/provider name format upstream of the setter"],"exampleFix":"// before\nset(path, \"model\", \"\\n\")?;\n// after\nlet v = raw.trim();\nanyhow::ensure!(!v.is_empty() && !v.chars().any(char::is_control));\nset(path, \"model\", v)?;","handlingStrategy":"validation","validationCode":"let v = value.trim();\nif v.is_empty() || v.chars().any(char::is_control) { return Err(anyhow!(\"route value must be nonempty, control-char free\")); }","typeGuard":"fn is_clean_nonempty(s: &str) -> bool { !s.trim().is_empty() && !s.chars().any(char::is_control) }","tryCatchPattern":null,"preventionTips":["Trim and sanitize all user-supplied model/provider names","Reject clipboard-pasted values with control bytes","Never interpolate possibly-empty env vars directly into route values"],"tags":["config","validation","input-sanitization"],"backgroundTag":"empty-required-field","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"}