{"record":{"id":"bee405bf4b4a89a6","repo":"BigPizzaV3/CodexPlusPlus","slug":"remote-control-recovery-requires-a-valid-thread-id","errorCode":null,"errorMessage":"Remote Control recovery requires a valid thread id","messagePattern":"Remote Control recovery requires a valid thread id","errorType":"validation","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"crates/codex-plus-core/src/remote_control_recovery.rs","lineNumber":150,"sourceCode":"        }\n        Err(error) => Err(error.into()),\n    }\n}\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(),","sourceCodeStart":132,"sourceCodeEnd":168,"githubUrl":"https://github.com/BigPizzaV3/CodexPlusPlus/blob/1f431ae49b57b3055e0e6845ba6156c6b4232b4d/crates/codex-plus-core/src/remote_control_recovery.rs#L132-L168","documentation":"Thrown by validate_request in remote_control_recovery.rs, which guards every PendingRemoteControlRecovery record before it is persisted to the pending-recovery state file. The thread_id must be a non-blank string of at most 128 bytes after trimming; blank or oversized ids are rejected so the on-disk queue never holds records that can never be matched back to a real codex thread.","triggerScenarios":"Calling record_pending_remote_control_recovery (or complete_pending_remote_control_recovery flows that re-validate) with a PendingRemoteControlRecovery whose thread_id.trim().is_empty() is true, or whose thread_id length exceeds 128 bytes (e.g. an id built by concatenating ids, or a whole session title pasted as the id).","commonSituations":"Frontend sends an empty string because the thread was never opened; a bridge caller passes null coerced to \"\"; the id is generated by joining multiple identifiers and grows past 128 bytes; whitespace-only id from a mis-trimmed form field.","solutions":["Pass the real codex thread id (the session file stem / thread_id from the session list), non-empty and <= 128 bytes","Trim the id at the call site before building the record","If the id legitimately exceeds 128 bytes, store a hash or mapping instead of the raw value","Add a UI-side length check so users see the problem before the request reaches the core"],"exampleFix":"// before\nlet req = PendingRemoteControlRecovery {\n    thread_id: format!(\"{a}-{b}-{c}\", /* ... */),\n    // ...\n};\n// after\nlet thread_id = a.to_string(); // single real thread id\nassert!(!thread_id.trim().is_empty() && thread_id.len() <= 128);","handlingStrategy":"validation","validationCode":"fn valid_thread_id(id: &str) -> bool {\n    !id.trim().is_empty() && id.len() <= 128\n}\n\nif !valid_thread_id(&req.thread_id) {\n    return Err(anyhow::anyhow!(\"thread id must be 1..=128 bytes, got {}\", req.thread_id.len()));\n}","typeGuard":"fn valid_thread_id(id: &str) -> bool {\n    !id.trim().is_empty() && id.len() <= 128\n}","tryCatchPattern":"match record_pending_remote_control_recovery(path, req) {\n    Err(e) if e.to_string().contains(\"valid thread id\") => { /* drop the record, log thread_id length */ }\n    other => other?,\n}","preventionTips":["Derive thread_id only from the codex session list / file stem, never from user free text","Enforce a 128-byte input maxlength in the frontend field","Unit-test the boundary: empty, whitespace-only, 128 bytes (ok), 129 bytes (reject)"],"tags":["remote-control","recovery","validation","thread-id","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"}