From 47aa1f3b6affa3b29e7c94ba56bdecb299a1c095 Mon Sep 17 00:00:00 2001 From: Charley Cunningham Date: Mon, 26 Jan 2026 17:12:17 -0800 Subject: [PATCH] Reject request_user_input outside Plan/Pair (#9955) ## Context Previous work in https://github.com/openai/codex/pull/9560 only rejected `request_user_input` in Execute and Custom modes. Since then, additional modes (e.g., Code) were added, so the guard should be mode-agnostic. ## What changed - Switch the handler to an allowlist: only Plan and PairProgramming are allowed - Return the same error for any other mode (including Code) - Add a Code-mode rejection test alongside the existing Execute/Custom tests ## Why This prevents `request_user_input` from being used in modes where it is not intended, even as new modes are introduced. --- AGENTS.md | 1 + .../core/src/tools/handlers/request_user_input.rs | 14 ++++++++------ codex-rs/core/tests/suite/request_user_input.rs | 13 +++++++++++++ 3 files changed, 22 insertions(+), 6 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index a0dcb98728..4b6f8992e4 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,6 +11,7 @@ In the codex-rs folder where the rust code lives: - Always collapse if statements per https://rust-lang.github.io/rust-clippy/master/index.html#collapsible_if - Always inline format! args when possible per https://rust-lang.github.io/rust-clippy/master/index.html#uninlined_format_args - Use method references over closures when possible per https://rust-lang.github.io/rust-clippy/master/index.html#redundant_closure_for_method_calls +- When possible, make `match` statements exhaustive and avoid wildcard arms. - When writing tests, prefer comparing the equality of entire objects over fields one by one. - When making a change that adds or changes an API, ensure that the documentation in the `docs/` folder is up to date if applicable. - If you change `ConfigToml` or nested config types, run `just write-config-schema` to update `codex-rs/core/config.schema.json`. diff --git a/codex-rs/core/src/tools/handlers/request_user_input.rs b/codex-rs/core/src/tools/handlers/request_user_input.rs index d2c55b08df..112dcae21e 100644 --- a/codex-rs/core/src/tools/handlers/request_user_input.rs +++ b/codex-rs/core/src/tools/handlers/request_user_input.rs @@ -36,12 +36,14 @@ impl ToolHandler for RequestUserInputHandler { } }; - let disallowed_mode = match session.collaboration_mode().await.mode { - ModeKind::Execute => Some("Execute"), - ModeKind::Custom => Some("Custom"), - _ => None, - }; - if let Some(mode_name) = disallowed_mode { + let mode = session.collaboration_mode().await.mode; + if !matches!(mode, ModeKind::Plan | ModeKind::PairProgramming) { + let mode_name = match mode { + ModeKind::Code => "Code", + ModeKind::Execute => "Execute", + ModeKind::Custom => "Custom", + ModeKind::Plan | ModeKind::PairProgramming => unreachable!(), + }; return Err(FunctionCallError::RespondToModel(format!( "request_user_input is unavailable in {mode_name} mode" ))); diff --git a/codex-rs/core/tests/suite/request_user_input.rs b/codex-rs/core/tests/suite/request_user_input.rs index 2d81fc9ad8..3872969610 100644 --- a/codex-rs/core/tests/suite/request_user_input.rs +++ b/codex-rs/core/tests/suite/request_user_input.rs @@ -286,6 +286,19 @@ async fn request_user_input_rejected_in_execute_mode() -> anyhow::Result<()> { .await } +#[tokio::test(flavor = "multi_thread", worker_threads = 2)] +async fn request_user_input_rejected_in_code_mode() -> anyhow::Result<()> { + assert_request_user_input_rejected("Code", |model| CollaborationMode { + mode: ModeKind::Code, + settings: Settings { + model, + reasoning_effort: None, + developer_instructions: None, + }, + }) + .await +} + #[tokio::test(flavor = "multi_thread", worker_threads = 2)] async fn request_user_input_rejected_in_custom_mode() -> anyhow::Result<()> { assert_request_user_input_rejected("Custom", |model| CollaborationMode {