{"record":{"id":"5f0793511936f886","repo":"Hmbown/CodeWhale","slug":"model-must-be-nonempty-and-contain-no-control-characters","errorCode":null,"errorMessage":"model must be nonempty and contain no control characters","messagePattern":"model must be nonempty and contain no control characters","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/config_persistence.rs","lineNumber":163,"sourceCode":"            }\n            set_provider_model_document(\n                doc,\n                identity.provider,\n                identity.persisted_id().unwrap_or(&identity.key),\n                model,\n            )?;\n        }\n    }\n    set_document_value(doc, &[\"route_preferences_version\"], 1_i64)\n}\n\npub(crate) fn set_provider_model_document(\n    doc: &mut toml_edit::DocumentMut,\n    provider: ApiProvider,\n    provider_identity: &str,\n    model: &str,\n) -> anyhow::Result<()> {\n    anyhow::ensure!(\n        !model.trim().is_empty() && !model.chars().any(char::is_control),\n        \"model must be nonempty and contain no control characters\"\n    );\n    let config: crate::config::Config = toml::from_str(&doc.to_string()).map_err(|_| {\n        anyhow::anyhow!(\"Could not parse destination route identity; contents omitted\")\n    })?;\n    let identity = config\n        .resolve_provider_pin_identity(provider_identity)\n        .map_err(anyhow::Error::msg)?;\n    anyhow::ensure!(\n        identity.provider == provider,\n        \"The destination config has a different provider identity\"\n    );\n    let provider_key = if provider == ApiProvider::Custom {\n        if identity.persisted_id().is_none() {\n            return set_document_value(doc, &[\"default_text_model\"], model);\n        }\n        identity.key","sourceCodeStart":145,"sourceCodeEnd":181,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/config_persistence.rs#L145-L181","documentation":"set_provider_model_document writes a provider's model into the TOML document, but first validates the model string: it must be non-empty after trimming and contain no control characters, since control characters would corrupt the config file or downstream requests. This ensure! rejects empty, whitespace-only, or control-character-bearing model names.","triggerScenarios":"Calling any of the callers (persist_provider_selection, persist_provider_model_key, migrate_legacy_route_preferences, reconcile_root_model_aliases) with model == \"\", a whitespace string, or a string containing \\n, \\t, \\r or other control chars.","commonSituations":"A model field read from a broken input source (empty env var, blank UI field); pasting a model id with embedded newline; upstream listing code returning empty strings for missing models.","solutions":["Trim and validate the model string before calling any persistence API; reject empty/control chars at the input boundary","Fix the upstream source of the model name (env var, config field, model list) so it supplies a real model id","Sanitize pasted/CLI model identifiers by stripping whitespace and rejecting control characters"],"exampleFix":"// before\npersist_provider_model_key(provider, identity, \"\\n\")?;\n// after\nlet model = raw_model.trim();\nanyhow::ensure!(!model.is_empty() && !model.chars().any(char::is_control));\npersist_provider_model_key(provider, identity, model)?;","handlingStrategy":"validation","validationCode":"fn valid_model_name(m: &str) -> bool {\n    let t = m.trim();\n    !t.is_empty() && !m.chars().any(char::is_control)\n}\n// gate every persistence call on valid_model_name(model)","typeGuard":null,"tryCatchPattern":"match persist_provider_model_key(provider, identity, model) {\n    Err(e) if e.to_string().contains(\"model must be nonempty\") => {\n        reprompt_for_model();\n    }\n    other => other?,\n}","preventionTips":["Trim model input at the CLI/UI boundary and reject empty values immediately","Sanitize pasted model ids (strip control characters)","Never persist a model field sourced from an unset env var without a default"],"tags":["config","validation","model-name"],"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"}