Skip to content

fix(xai-sanitize): deepcopy tools_for_api before in-place mutation (#27907)#34416

Merged
teknium1 merged 1 commit into
mainfrom
fix/27907-xai-sanitize-deepcopy
May 29, 2026
Merged

fix(xai-sanitize): deepcopy tools_for_api before in-place mutation (#27907)#34416
teknium1 merged 1 commit into
mainfrom
fix/27907-xai-sanitize-deepcopy

Conversation

@teknium1

Copy link
Copy Markdown
Contributor

Summary

The xAI tool-schema sanitizers (strip_slash_enum, strip_pattern_and_format) mutate their input in place — that's their documented contract. Two call sites (chat_completion_helpers.build_api_kwargs + the auxiliary client) were passing agent.tools straight through, so the first xAI request permanently stripped slash-containing enum constraints and pattern/format keywords from the per-agent tool registry.

Any subsequent non-xAI call from the same agent (auxiliary task routed to Anthropic, OpenRouter fallback, mid-session model switch) saw the already-stripped schema. Silent constraint loss with no way for the user to notice from their config.

Salvages the deepcopy correctness contribution from PR #27907 (@gbarany).

Background — what's already on main vs. what's missing

The slash-enum functional bug (xAI 400ing on enums with /) was fixed earlier by PR #32443 (@Nami4D) — that landed the strip_slash_enum integration at the right boundaries, but used the sanitizers directly without copying. That left the mutation bug unaddressed.

@gbarany's PR #27907 surfaced this in detail with a tested deepcopy fix. This salvage takes the correctness contribution and skips:

Changes

  • agent/chat_completion_helpers.py:587-617_copy.deepcopy(tools_for_api) before the existing strip_pattern_and_format + strip_slash_enum calls.
  • agent/auxiliary_client.py:704-722 — same fix; list(tools) was only a shallow copy, the inner parameter dicts were still references.

Validation

3 new tests in tests/run_agent/test_run_agent_codex_responses.py:

Test Locks in
strips_slash_enum_from_outgoing_request Outgoing xAI kwargs has no slash-containing enums (functional contract preserved)
does_not_mutate_agent_tools Headline #27907 regression: snapshot agent.tools before, assert it survives intact after. Pre-fix this assertion would have caught the mutation
is_idempotent_across_repeated_calls Three xAI requests in a row each strip cleanly AND don't progressively erode the source schema
scripts/run_tests.sh tests/agent/test_auxiliary_client.py \
                     tests/agent/transports/test_codex_transport.py \
                     tests/run_agent/test_run_agent_codex_responses.py \
                     tests/tools/test_schema_sanitizer.py
=== 344 tests passed, 0 failed in 18.0s ===

Credit

@gbarany did the careful empirical work on xAI's enum validator (mapping which values get rejected — application/json rejected, text/* rejected, foo*bar accepted, etc.) AND identified the mutation issue. Salvage keeps their correctness fix and authorship; the parallel sanitizer + preflight-neutrality argument get dropped because #32443 already covered the same ground a different way.

…27907)

The xAI tool-schema sanitizers (strip_slash_enum, strip_pattern_and_format)
mutate their input in place — that's their documented contract. The two
call sites (chat_completion_helpers.build_api_kwargs and the auxiliary
client) were passing agent.tools straight through, so the first xAI
request would permanently strip slash-containing enum constraints and
pattern/format keywords from the per-agent tool registry.

Effect: any subsequent non-xAI call from the same agent (auxiliary task
routed to Anthropic, OpenRouter fallback, mid-session model switch) saw
the already-stripped schema with no way for the user to notice from
their config.

Fix: deepcopy tools_for_api before sanitizing at both call sites.

The slash-enum bug itself (xAI 400ing on enums with '/') was fixed
earlier by #32443 (Nami4D) — that PR landed the strip but used the
sanitizers directly without copying. This salvages #27907's correctness
contribution (the deepcopy) while skipping its redundant parallel
sanitizer (strip_xai_incompatible_enum_values is functionally
equivalent to the existing strip_slash_enum) and its preflight-
neutrality argument (we chose model-gated preflight in #32443).

3 new tests in tests/run_agent/test_run_agent_codex_responses.py:

- strips_slash_enum_from_outgoing_request — outgoing kwargs has no
  slash-containing enum values (functional contract preserved).
- does_not_mutate_agent_tools — headline #27907 regression. Snapshot
  agent.tools before build_api_kwargs, assert it survives intact
  after. Pre-fix this assertion would have caught the mutation.
- is_idempotent_across_repeated_calls — three xAI requests in a row
  each strip cleanly AND don't progressively erode the source schema.

344/344 across tests/agent/test_auxiliary_client.py,
tests/agent/transports/test_codex_transport.py,
tests/run_agent/test_run_agent_codex_responses.py, and
tests/tools/test_schema_sanitizer.py.

Co-authored-by: Gabor Barany <barany.gabor@gmail.com>
@github-actions

Copy link
Copy Markdown
Contributor

🔎 Lint report: fix/27907-xai-sanitize-deepcopy vs origin/main

ruff

Total: 0 on HEAD, 0 on base (➖ 0)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 0 pre-existing issues carried over.

ty (type checker)

Total: 9422 on HEAD, 9419 on base (🆕 +3)

🆕 New issues: none

✅ Fixed issues: none

Unchanged: 4890 pre-existing issues carried over.

Diagnostics are surfaced as warnings — this check never fails the build.

@teknium1 teknium1 merged commit 1386a7e into main May 29, 2026
24 checks passed
@teknium1 teknium1 deleted the fix/27907-xai-sanitize-deepcopy branch May 29, 2026 06:30
@alt-glitch alt-glitch added P2 Medium — degraded but workaround exists type/bug Something isn't working comp/agent Core agent loop, run_agent.py, prompt builder labels May 29, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

comp/agent Core agent loop, run_agent.py, prompt builder P2 Medium — degraded but workaround exists type/bug Something isn't working

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants