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
This commit is contained in:
rhan-oai
2026-08-20 19:37:07 +00:00
committed by copyberry
parent aead844f64
commit 010738a25c
3 changed files with 19 additions and 1 deletions

View File

@@ -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 threads 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 threads 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 threads 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`.

View File

@@ -824,6 +824,8 @@ impl TurnRequestProcessor {
params: ThreadSettingsUpdateParams,
) -> Result<ThreadSettingsUpdateResponse, JSONRPCErrorError> {
let (_, thread) = self.load_thread(&params.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(

View File

@@ -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