{"record":{"id":"8e5c620aa8ce897e","repo":"BigPizzaV3/CodexPlusPlus","slug":"remote-control-recovery-requires-profile-and-provi","errorCode":null,"errorMessage":"Remote Control recovery requires profile and provider provenance","messagePattern":"Remote Control recovery requires profile and provider provenance","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/remote_control_recovery.rs","lineNumber":153,"sourceCode":"}\n\nfn save_state(path: &Path, state: &PendingRemoteControlRecoveryState) -> anyhow::Result<()> {\n    if state.requests.is_empty() {\n        match std::fs::remove_file(path) {\n            Ok(()) => return Ok(()),\n            Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),\n            Err(error) => return Err(error.into()),\n        }\n    }\n    atomic_write(path, serde_json::to_string_pretty(state)?.as_bytes())\n}\n\nfn validate_request(request: &PendingRemoteControlRecovery) -> anyhow::Result<()> {\n    if request.thread_id.trim().is_empty() || request.thread_id.len() > 128 {\n        anyhow::bail!(\"Remote Control recovery requires a valid thread id\");\n    }\n    if request.profile_id.trim().is_empty() || request.target_provider.trim().is_empty() {\n        anyhow::bail!(\"Remote Control recovery requires profile and provider provenance\");\n    }\n    Ok(())\n}\n\n#[cfg(test)]\nmod tests {\n    use super::*;\n    use tempfile::tempdir;\n\n    fn request(thread_id: &str) -> PendingRemoteControlRecovery {\n        PendingRemoteControlRecovery {\n            thread_id: thread_id.to_string(),\n            profile_id: \"official-mix\".to_string(),\n            target_provider: \"custom\".to_string(),\n            config_generation: \"generation\".to_string(),\n            created_at: 1,\n        }\n    }","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/remote_control_recovery.rs#L135-L171","documentation":"Second guard in validate_request (remote_control_recovery.rs): a pending recovery record must carry provenance — a non-blank profile_id and a non-blank target_provider. Without both fields the recovery queue entry could not be replayed to the right provider profile later, so persistence is refused up front.","triggerScenarios":"Calling record_pending_remote_control_recovery with a PendingRemoteControlRecovery whose profile_id.trim() or target_provider.trim() is empty — e.g. the frontend built the record before the active profile was loaded, or the provider name field was never populated for a hand-written profile.","commonSituations":"Race where the recovery record is captured during startup before settings finish loading; a test fixture that only sets thread_id; a profile created without a provider label; null coerced to empty string through JSON deserialization.","solutions":["Populate profile_id with the active RelayProfile.id and target_provider with the provider name before recording the pending recovery","Defer recording until both settings and active profile are loaded","Fix the fixture/test to include provenance fields","Log the record fields at the call site when validation fails to catch silent empty strings"],"exampleFix":"// before\nPendingRemoteControlRecovery {\n    thread_id: tid.clone(),\n    profile_id: String::new(),\n    target_provider: String::new(),\n    // ...\n}\n// after\nPendingRemoteControlRecovery {\n    thread_id: tid.clone(),\n    profile_id: settings.active_relay_profile().id.to_string(),\n    target_provider: settings.active_relay_profile().name.clone(),\n    // ...\n}","handlingStrategy":"validation","validationCode":"if request.profile_id.trim().is_empty() || request.target_provider.trim().is_empty() {\n    // defer recording until settings + active profile are loaded\n    return Ok(());\n}","typeGuard":"fn has_provenance(r: &PendingRemoteControlRecovery) -> bool {\n    !r.profile_id.trim().is_empty() && !r.target_provider.trim().is_empty()\n}","tryCatchPattern":"match record_pending_remote_control_recovery(path, req) {\n    Err(e) if e.to_string().contains(\"provenance\") => { /* re-enqueue after profile load */ }\n    other => other?,\n}","preventionTips":["Capture provenance at the same moment the recovery is triggered, from the same settings snapshot","Never build PendingRemoteControlRecovery from default::default()","Add a serde default + post-load assertion so missing fields fail loudly in tests"],"tags":["remote-control","recovery","validation","provenance","codex-plus-core"],"backgroundTag":"request-validation-failed","analyzedSha":"1f431ae49b57b3055e0e6845ba6156c6b4232b4d","analyzedAt":"2026-08-16T20:54:18.598Z","schemaVersion":2},"datasetVersion":"2026-08-16T23:17:17.608Z"}