xAI grok models rejecting reasoning.effort parameter
Cluster 23088 · agent/transports. Data as of
Nothing open on GitHub. Fix merged in #23435 on 10 May 2026. Check it covers your case.
The short answer
- Known?
- Yes. 4 related items grouped here, 1 issue already closed.
- Fix event
- A fix was merged to main in PR #23435. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
unresolved. That is separate from the fix event. - Still open
- Nothing open on GitHub.
These issues and fixes address HTTP 400 errors caused by sending the reasoning.effort parameter to xAI grok models that do not accept it. The work involves conditionally omitting or gating that parameter based on the specific model being used.
Open issues0
Open PRs0
Fixes on record
-
#23435 fix(xai): omit reasoning.effort for grok models that reject it
files:
agent/model_metadata.py,agent/transports/codex.py,tests/agent/transports/test_codex_transport.pyShow the diff
-
agent/model_metadata.py@@ -244,6 +244,44 @@ def _strip_provider_prefix(model: str) -> str: "zai-org/GLM-5": 202752, } +# xAI Grok models that ACCEPT the `reasoning.effort` parameter on +# api.x.ai. Verified live against /v1/responses 2026-05-10: +# +# ACCEPTS effort: grok-3-mini, grok-3-mini-fast, grok-4.20-multi-agent-0309, +# grok-4.3 +# REJECTS effort: grok-3, grok-4, grok-4-0709, grok-4-fast-(non-)reasoning, +# grok-4-1-fast-(non-)reasoning, grok-4.20-0309-(non-)reasoning, +# grok-code-fast-1 +# +# REJECTS-side models still reason natively — they just don't expose an +# effort dial — so callers should send no `reasoning` key at all rather +# than a default `medium` (which 400s with "Model X does not support +# parameter reasoningEffort"). +_GROK_EFFORT_CAPABLE_PREFIXES = ( + "grok-3-mini", + "grok-4.20-multi-agent", + "grok-4.3", +) + + +def grok_supports_reasoning_effort(model: str) -> bool: + """Return True when an xAI Grok model accepts ``reasoning.effort``. + + Allowlist by substring (matches both bare ``grok-3-mini`` and + aggregator-prefixed ``x-ai/grok-3-mini``). Conservative by design: + if a future Grok model isn't listed, we send no effort dial rather + than 400. + """ + name = (model or "").strip().lower() + if not name: + return False + # Strip common aggregator prefixes (x-ai/, openrouter/x-ai/, xai/, ...) + for sep in ("/",): + if sep in name: + name = name.rsplit(sep, 1)[-1] + return any(name.startswith(prefix) for prefix in _GROK_EFFORT_CAPABLE_PREFIXES) + + _CONTEXT_LENGTH_KEYS = ( "context_length", "context_window", -
agent/transports/codex.py@@ -104,8 +104,16 @@ def build_kwargs( kwargs["prompt_cache_key"] = session_id if reasoning_enabled and is_xai_responses: + from agent.model_metadata import grok_supports_reasoning_effort + kwargs["include"] = ["reasoning.encrypted_content"] - kwargs["reasoning"] = {"effort": reasoning_effort} + # xAI rejects `reasoning.effort` on grok-4 / grok-4-fast / grok-3 + # / grok-code-fast / grok-4.20-0309-* with HTTP 400 even though + # those models reason natively. Only send the effort dial when + # the target model is on the allowlist; otherwise send no + # `reasoning` key at all and let the model reason on its own. + if grok_supports_reasoning_effort(model): + kwargs["reasoning"] = {"effort": reasoning_effort} elif reasoning_enabled: if is_github_responses: github_reasoning = params.get("github_reasoning_extra") -
tests/agent/transports/test_codex_transport.py@@ -180,6 +180,119 @@ def test_xai_minimal_effort_clamped(self, transport): # "minimal" should be clamped to "low" for xAI as well assert kw.get("reasoning", {}).get("effort") == "low" + # --- Grok reasoning-effort capability allowlist --- + # api.x.ai 400s with "Model X does not support parameter reasoningEffort" + # on grok-4 / grok-4-fast / grok-3 / grok-code-fast / grok-4.20-0309-*. + # Those models reason natively but don't expose the dial. The transport + # must omit the `reasoning` key for them while keeping the encrypted + # reasoning content include so we can capture native reasoning tokens. + + def test_xai_grok_4_omits_reasoning_effort(self, transport): + """grok-4 / grok-4-0709 reject reasoning.effort with HTTP 400.""" + messages = [{"role": "user", "content": "Hi"}] + for model in ("grok-4", "grok-4-0709"): + kw = transport.build_kwargs( + model=model, messages=messages, tools=[], + is_xai_responses=True, + reasoning_config={"effort": "high"}, + ) + assert "reasoning" not in kw, ( + f"{model} must not receive a reasoning key (xAI rejects it)" + ) + # Still capture native reasoning tokens + assert "reasoning.encrypted_content" in kw.get("include", []) + + def test_xai_grok_4_fast_omits_reasoning_effort(self, transport): + """grok-4-fast and grok-4-1-fast variants reject reasoning.effort.""" + messages = [{"role": "user", "content": "Hi"}] + for model in ( + "grok-4-fast-reasoning", + "grok-4-fast-non-reasoning", + "grok-4-1-fast-reasoning", + "grok-4-1-fast-non-reasoning", + ): + kw = transport.build_kwargs( + model=model, messages=messages, tools=[], + is_xai_responses=True, + reasoning_config={"effort": "low"}, + ) + assert "reasoning" not in kw, ( + f"{model} must not receive a reasoning key (xAI rejects it)" + ) + + def test_xai_grok_3_non_mini_omits_reasoning_effort(self, transport): + """Plain grok-3 rejects reasoning.effort — only grok-3-mini accepts it.""" + messages = [{"role": "user", "content": "Hi"}] + kw = transport.build_kwargs( + model="grok-3", messages=messages, tools=[], + is_xai_responses=True, + reasoning_config={"effort": "medium"}, + ) + assert "reasoning" not in kw + + def test_xai_grok_3_mini_keeps_reasoning_effort(self, transport): + """grok-3-mini and -fast variants do accept the effort dial.""" + messages = [{"role": "user", "content": "Hi"}] + for model in ("grok-3-mini", "grok-3-mini-fast"): + kw = transport.build_kwargs( + model=model, messages=messages, tools=[], + is_xai_responses=True, + reasoning_config={"effort": "high"}, + ) + assert kw.get("reasoning") == {"effort": "high"} + + def test_xai_grok_4_20_0309_variants_omit_reasoning_effort(self, transport): + """grok-4.20-0309-(non-)reasoning reject the effort dial. + + Counterintuitively, only grok-4.20-multi-agent-0309 accepts it. + """ + messages = [{"role": "user", "content": "Hi"}] + for model in ("grok-4.20-0309-reasoning", "grok-4.20-0309-non-reasoning"): + kw = transport.build_kwargs( + model=model, messages=messages, tools=[], + is_xai_responses=True, + reasoning_config={"effort": "high"}, + ) + assert "reasoning" not in kw, f"{model} must not receive reasoning" + + def test_xai_grok_4_20_multi_agent_keeps_reasoning_effort(self, transport): + """grok-4.20-multi-agent-0309 is the one grok-4.20 variant that accepts effort.""" + messages = [{"role": "user", "content": "Hi"}] + kw = transport.build_kwargs( + model="grok-4.20-multi-agent-0309", messages=messages, tools=[], + is_xai_responses=True, + reasoning_config={"effort": "low"}, + ) + assert kw.get("reasoning") == {"effort": "low"} + + def test_xai_grok_code_fast_omits_reasoning_effort(self, transport): + """grok-code-fast-1 rejects reasoning.effort.""" + messages = [{"role": "user", "content": "Hi"}] + kw = transport.build_kwargs( + model="grok-code-fast-1", messages=messages, tools=[], + is_xai_responses=True, + reasoning_config={"effort": "high"}, + ) + assert "reasoning" not in kw + + def test_xai_aggregator_prefix_stripped(self, transport): + """`x-ai/grok-3-mini` (OpenRouter-style slug) still resolves correctly.""" + messages = [{"role": "user", "content": "Hi"}] + # Effort-capable + kw = transport.build_kwargs( + model="x-ai/grok-3-mini", messages=messages, tools=[], + is_xai_responses=True, + reasoning_config={"effort": "high"}, + ) + assert kw.get("reasoning") == {"effort": "high"} + # Effort-incapable + kw = transport.build_kwargs( + model="x-ai/grok-4-0709", messages=messages, tools=[], + is_xai_responses=True, + reasoning_config={"effort": "high"}, + ) + assert "reasoning" not in kw + class TestCodexValidateResponse:
-
2 PRs closed without merging
From the close comments: done elsewhere 1 · superseded by another PR 1
Closed issues · 1
- Closed#23088[Bug]: xAI grok-4-1-fast returns HTTP 400 — "does not support parameter reasoningEffort"
How this cluster was grouped
-
Link evidence (
structural links throughout): every member is connected by structural evidence: shared changed files, or a closing/superseded reference. -
Pairwise link density
0.50: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the LabAt a glance
- Subsystem
- agent/transports
- Merged PRs
- #23435
- Closed issues
- 1
- Attempts
- 2 PRs closed without merging
- Fix link checked
- 10 May 2026