openai/codex · error · anyhow::Error

{method} rejected legacy params

Error message

{method} rejected legacy params

What it means

The app-server-daemon remote-control client calls remoteControl/enable or remoteControl/disable twice: first with a params payload such as {"ephemeral":true}, then, if the server answers JSON-RPC -32602, once more with params omitted for older servers (request_remote_control_with_legacy_fallback). This error means the second, legacy-shaped attempt was also rejected with Invalid params, so the connected app-server accepts neither parameter shape and the RPC cannot proceed.

Source

Thrown at codex-rs/app-server-daemon/src/remote_control_client.rs:185

        Some(params),
    )
    .await?;
    match read_remote_control_response(websocket, &REMOTE_CONTROL_REQUEST_ID, method).await? {
        RemoteControlRpcResponse::Success(response) => Ok(response),
        RemoteControlRpcResponse::InvalidParams => {
            send_remote_control_request(
                websocket,
                REMOTE_CONTROL_REQUEST_ID.clone(),
                method,
                /*params*/ None,
            )
            .await?;
            match read_remote_control_response(websocket, &REMOTE_CONTROL_REQUEST_ID, method)
                .await?
            {
                RemoteControlRpcResponse::Success(response) => Ok(response),
                RemoteControlRpcResponse::InvalidParams => {
                    Err(anyhow!("{method} rejected legacy params"))
                }
            }
        }
    }
}

async fn connect_with_retry(
    socket_path: &Path,
    connect_timeout: Duration,
    connect_retry_delay: Duration,
) -> Result<WebSocketStream<codex_uds::UnixStream>> {
    let deadline = Instant::now() + connect_timeout;
    loop {
        match client::connect(socket_path).await {
            Ok(websocket) => return Ok(websocket),
            Err(_) if Instant::now() < deadline => {
                sleep(connect_retry_delay).await;
            }

View on GitHub (pinned to 339751715c)

Solutions

  1. Align versions: reinstall or update codex so the daemon and the app-server binary come from the same release, then retry; mismatched param schemas between the two are the dominant cause.
  2. Identify the serving binary: read the userAgent in the initialize response (for example codex_app_server/1.2.3) and compare it with the daemon version.
  3. If you control the server or a test mock, implement the fallback contract: answer -32602 to the {"ephemeral":true} request and a valid response to the follow-up request with params null, as the tests in remote_control_client.rs do.
  4. If versions match and it persists, capture the websocket frames to log the server's actual InvalidParams message and report it against the app-server remoteControl handler.

Example fix

// before (mock server): always answers -32602
if method == "remoteControl/disable" {
    reply_error(id, -32602, "Invalid params"); // daemon fails: rejected legacy params
}

// after (mock server): reject params once, then answer the param-less retry
if method == "remoteControl/disable" && req.params.is_some() {
    reply_error(id, -32602, "Invalid params");
} else if method == "remoteControl/disable" {
    reply_result(id, disable_response()); // legacy shape accepted
}
Defensive patterns

Strategy: try-catch

Validate before calling

// After client::initialize, compare the server's reported version with the
// daemon's before issuing remoteControl RPCs (sketch):
let ua = initialize_result["userAgent"].as_str().unwrap_or_default();
if !same_release(ua, daemon_version()) {
    return Err(anyhow!("app-server {ua} differs from daemon {}; align installs", daemon_version()));
}

Try / catch

match enable_remote_control(&socket_path).await {
    Ok(status) => { /* proceed */ }
    Err(err) if err.to_string().contains("rejected legacy params") => {
        // Both param shapes refused: treat as version skew and surface an
        // actionable error instead of retrying the same request.
        return Err(err.context("app-server rejected every remoteControl param shape; daemon and app-server versions likely differ"));
    }
    Err(err) => return Err(err),
}

Prevention

When it happens

Trigger: Invoking disable_remote_control or enable_remote_control_with_timeout (and its wrappers enable_remote_control / enable_remote_control_with_connect_retry) against an app-server whose remoteControl handler returns -32602 for both the parametrized request and the param-less retry: a build with a different remoteControl params schema, a much older or newer codex binary than the daemon, or a test double that unconditionally replies InvalidParams.

Common situations: Version skew between the codex CLI and the app-server it spawns (npm global vs ~/.codex/bin mixed installs); partial upgrades where one binary updated and the other did not; integration-test mock servers that answer -32602 without implementing the retry-then-succeed fallback contract.

Related errors


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/d5655d96519aebce. Report an issue: GitHub.