From bd693e59422729e530f5ccfa2f9bc3dc2611158b Mon Sep 17 00:00:00 2001 From: Illia Oleksiuk Date: Tue, 12 May 2026 17:10:59 -0700 Subject: [PATCH] fix: avoid mutating codex output schema input MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Mirror the MCP fix in #3134 (and the FunctionTool fix in #3382) for the Codex output_schema path. `_resolve_output_schema` currently does `schema = dict(option)`, which is only a shallow copy — nested user dicts (e.g. each entry in `option["properties"]`) are the same identity as the caller's. `ensure_strict_json_schema` then mutates those nested dicts in place (it is documented as mutating its input), leaking strict-mode side effects like `additionalProperties: false` and `required: [...]` back into the caller's stored schema. Switch to `copy.deepcopy(dict(option))` so the strict conversion only mutates the local copy, matching the precedent established by #3134. --- .../extensions/experimental/codex/codex_tool.py | 3 ++- .../experiemental/codex/test_codex_tool.py | 14 ++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/src/agents/extensions/experimental/codex/codex_tool.py b/src/agents/extensions/experimental/codex/codex_tool.py index 854aa65fc9..cf0eee08cd 100644 --- a/src/agents/extensions/experimental/codex/codex_tool.py +++ b/src/agents/extensions/experimental/codex/codex_tool.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +import copy import dataclasses import inspect import json @@ -694,7 +695,7 @@ def _resolve_output_schema( return _build_codex_output_schema(descriptor) if isinstance(option, Mapping): - schema = dict(option) + schema = copy.deepcopy(dict(option)) if "type" in schema and schema.get("type") != "object": raise UserError('Codex output schema must be a JSON object schema with type "object".') return ensure_strict_json_schema(schema) diff --git a/tests/extensions/experiemental/codex/test_codex_tool.py b/tests/extensions/experiemental/codex/test_codex_tool.py index 042e05bc01..9bf650816b 100644 --- a/tests/extensions/experiemental/codex/test_codex_tool.py +++ b/tests/extensions/experiemental/codex/test_codex_tool.py @@ -1,6 +1,7 @@ from __future__ import annotations import asyncio +import copy import dataclasses import importlib import inspect @@ -1516,6 +1517,19 @@ def test_codex_tool_resolve_output_schema_validation_errors() -> None: codex_tool_module._resolve_output_schema({"type": "string"}) +def test_codex_tool_resolve_output_schema_does_not_mutate_input() -> None: + nested = {"type": "object", "properties": {"y": {"type": "string"}}} + option = {"type": "object", "properties": {"inner": nested}} + option_snapshot = copy.deepcopy(option) + + result = codex_tool_module._resolve_output_schema(option) + + assert option == option_snapshot + assert nested == {"type": "object", "properties": {"y": {"type": "string"}}} + assert result is not None + assert result["properties"]["inner"] is not nested + + def test_codex_tool_resolve_output_schema_descriptor() -> None: descriptor = { "title": "Report",