From 010738a25c9f3dd77d11760bba9277124e1a45e8 Mon Sep 17 00:00:00 2001 From: rhan-oai Date: Thu, 20 Aug 2026 19:37:07 +0000 Subject: [PATCH] Reject settings updates for parent-owned subagents (#39792) ## What changed - Apply the existing direct-input restriction to `thread/settings/update` for parent-owned Multi-Agent V2 subagents. - Document the restriction and extend the direct-input test to verify that the request returns an invalid-request error. GitOrigin-RevId: 2eead01c6f54ec637c68573378f3b56a7ee85652 --- codex-rs/app-server/README.md | 2 +- .../src/request_processors/turn_processor.rs | 2 ++ codex-rs/app-server/tests/suite/v2/turn_start.rs | 16 ++++++++++++++++ 3 files changed, 19 insertions(+), 1 deletion(-) diff --git a/codex-rs/app-server/README.md b/codex-rs/app-server/README.md index 5b60156f21..0578eb560a 100644 --- a/codex-rs/app-server/README.md +++ b/codex-rs/app-server/README.md @@ -179,7 +179,7 @@ Example with notification opt-out: - `thread/searchOccurrences` — experimental; find literal, case-insensitive matches in visible user messages and summary-selected final assistant messages within one paginated thread. - `thread/metadata/update` — patch stored thread metadata in sqlite; supports updating persisted `gitInfo` fields and experimental `projectId`, then returns the refreshed `thread`. Omit `projectId` to preserve assignment and pass an empty string to clear it. - `thread/section/move` — atomically move a thread into the section identified by `sectionId`, before another thread or at the end when `beforeThreadId` is `null`. Reordering within the same section preserves `sectionEnteredAt`; entering a different section resets it. Set `sectionId` to `null` to remove the thread from its section. Returns `{}` on success. -- `thread/settings/update` — experimental; queue a partial update to a loaded thread’s next-turn settings without starting a turn or adding transcript items. Omitted fields leave settings unchanged; `serviceTier: null` clears the tier; deprecated `multiAgentMode` is ignored, while Ultra reasoning effort enables proactive multi-agent behavior; `sandboxPolicy` and `permissions` cannot be combined. Returns `{}` when the update is accepted and emits `thread/settings/updated` with the full effective settings only if they actually change. `turn/start` settings overrides emit the same notification when they change the stored settings. +- `thread/settings/update` — experimental; queue a partial update to a loaded thread’s next-turn settings without starting a turn or adding transcript items. Omitted fields leave settings unchanged; `serviceTier: null` clears the tier; deprecated `multiAgentMode` is ignored, while Ultra reasoning effort enables proactive multi-agent behavior; `sandboxPolicy` and `permissions` cannot be combined. Parent-owned Multi-Agent V2 subagents reject direct settings updates. Returns `{}` when the update is accepted and emits `thread/settings/updated` with the full effective settings only if they actually change. `turn/start` settings overrides emit the same notification when they change the stored settings. - `thread/memoryMode/set` — experimental; set a thread’s persisted memory eligibility to `"enabled"` or `"disabled"` for either a loaded thread or a stored rollout; returns `{}` on success. - `memory/reset` — experimental; clear the current `CODEX_HOME/memories` directory and reset persisted memory stage data in sqlite while preserving existing thread memory modes; returns `{}` on success. - `thread/goal/set` — create or update the single persisted goal for a materialized thread; returns the current goal and emits `thread/goal/updated`. diff --git a/codex-rs/app-server/src/request_processors/turn_processor.rs b/codex-rs/app-server/src/request_processors/turn_processor.rs index 822dcace40..7664364175 100644 --- a/codex-rs/app-server/src/request_processors/turn_processor.rs +++ b/codex-rs/app-server/src/request_processors/turn_processor.rs @@ -824,6 +824,8 @@ impl TurnRequestProcessor { params: ThreadSettingsUpdateParams, ) -> Result { let (_, thread) = self.load_thread(¶ms.thread_id).await?; + self.ensure_direct_input_allowed(request_id, thread.as_ref()) + .await?; let cwd = resolve_request_cwd(params.cwd)?; let environments = self .build_environment_override( diff --git a/codex-rs/app-server/tests/suite/v2/turn_start.rs b/codex-rs/app-server/tests/suite/v2/turn_start.rs index b264f66b72..ccf902b80f 100644 --- a/codex-rs/app-server/tests/suite/v2/turn_start.rs +++ b/codex-rs/app-server/tests/suite/v2/turn_start.rs @@ -48,6 +48,7 @@ use codex_app_server_protocol::ThreadDeletedNotification; use codex_app_server_protocol::ThreadItem; use codex_app_server_protocol::ThreadLoadedListParams; use codex_app_server_protocol::ThreadLoadedListResponse; +use codex_app_server_protocol::ThreadSettingsUpdateParams; use codex_app_server_protocol::ThreadSettingsUpdatedNotification; use codex_app_server_protocol::ThreadSource; use codex_app_server_protocol::ThreadStartParams; @@ -3893,6 +3894,21 @@ async fn direct_input_to_multi_agent_v2_subagent_is_rejected() -> Result<()> { assert_eq!(direct_steer_error.error.code, INVALID_REQUEST_ERROR_CODE); assert_eq!(direct_steer_error.error.message, ERROR_MESSAGE); + let direct_settings_req = mcp + .send_thread_settings_update_request(ThreadSettingsUpdateParams { + thread_id: child_thread_id.clone(), + permissions: Some(BUILT_IN_PERMISSION_PROFILE_DANGER_FULL_ACCESS.to_string()), + ..Default::default() + }) + .await?; + let direct_settings_error: JSONRPCError = timeout( + DEFAULT_READ_TIMEOUT, + mcp.read_stream_until_error_message(RequestId::Integer(direct_settings_req)), + ) + .await??; + assert_eq!(direct_settings_error.error.code, INVALID_REQUEST_ERROR_CODE); + assert_eq!(direct_settings_error.error.message, ERROR_MESSAGE); + let event = wait_for_matching_analytics_event(&server, DEFAULT_READ_TIMEOUT, |event| { event["event_type"] == "codex_collab_agent_tool_call_event" && event["event_params"]["item_id"] == SPAWN_CALL_ID