BigPizzaV3/CodexPlusPlus · error · anyhow::Error

Remote Control recovery requires profile and provider proven

Error message

Remote Control recovery requires profile and provider provenance

What it means

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.

Source

Thrown at crates/codex-plus-core/src/remote_control_recovery.rs:153

}

fn save_state(path: &Path, state: &PendingRemoteControlRecoveryState) -> anyhow::Result<()> {
    if state.requests.is_empty() {
        match std::fs::remove_file(path) {
            Ok(()) => return Ok(()),
            Err(error) if error.kind() == std::io::ErrorKind::NotFound => return Ok(()),
            Err(error) => return Err(error.into()),
        }
    }
    atomic_write(path, serde_json::to_string_pretty(state)?.as_bytes())
}

fn validate_request(request: &PendingRemoteControlRecovery) -> anyhow::Result<()> {
    if request.thread_id.trim().is_empty() || request.thread_id.len() > 128 {
        anyhow::bail!("Remote Control recovery requires a valid thread id");
    }
    if request.profile_id.trim().is_empty() || request.target_provider.trim().is_empty() {
        anyhow::bail!("Remote Control recovery requires profile and provider provenance");
    }
    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use tempfile::tempdir;

    fn request(thread_id: &str) -> PendingRemoteControlRecovery {
        PendingRemoteControlRecovery {
            thread_id: thread_id.to_string(),
            profile_id: "official-mix".to_string(),
            target_provider: "custom".to_string(),
            config_generation: "generation".to_string(),
            created_at: 1,
        }
    }

View on GitHub (pinned to 1f431ae49b)

Solutions

  1. Populate profile_id with the active RelayProfile.id and target_provider with the provider name before recording the pending recovery
  2. Defer recording until both settings and active profile are loaded
  3. Fix the fixture/test to include provenance fields
  4. Log the record fields at the call site when validation fails to catch silent empty strings

Example fix

// before
PendingRemoteControlRecovery {
    thread_id: tid.clone(),
    profile_id: String::new(),
    target_provider: String::new(),
    // ...
}
// after
PendingRemoteControlRecovery {
    thread_id: tid.clone(),
    profile_id: settings.active_relay_profile().id.to_string(),
    target_provider: settings.active_relay_profile().name.clone(),
    // ...
}
Defensive patterns

Strategy: validation

Validate before calling

if request.profile_id.trim().is_empty() || request.target_provider.trim().is_empty() {
    // defer recording until settings + active profile are loaded
    return Ok(());
}

Type guard

fn has_provenance(r: &PendingRemoteControlRecovery) -> bool {
    !r.profile_id.trim().is_empty() && !r.target_provider.trim().is_empty()
}

Try / catch

match record_pending_remote_control_recovery(path, req) {
    Err(e) if e.to_string().contains("provenance") => { /* re-enqueue after profile load */ }
    other => other?,
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@1f431ae49b (2026-08-16). Data as JSON: /api/errors/8e5c620aa8ce897e. Report an issue: GitHub.