Summary
Every Codex request through an Azure OpenAI Model Provider Service fails with HTTP 400. Codex ≥ 0.147 packages its built-in tools as an input-item tool whose description is an empty string, and the Azure OpenAI Responses API rejects an empty description where OpenAI direct accepts it. Codex is unusable against an Azure OpenAI MPS as a result — the very first turn fails, with or without MCP servers.
ERROR: {"error_code":"BAD_REQUEST","message":"Model Provider Service returned error with status code 400:
Invalid 'input[0].tools[0].description': empty string. Expected a string with minimum length 1,
but got an empty string instead. ...","details":[{"reason":"MODEL_PROVIDER_ERROR",
"metadata":{"provider":"azure_openai","upstream_status":"400",
"upstream_body":"{\"error\":{\"message\":\"Invalid 'input[0].tools[0].description': empty string...\",
\"type\":\"invalid_request_error\",\"param\":\"input[0].tools[0].description\",\"code\":\"empty_string\"}}"}}]}
Versions
|
|
| ucode |
0.0.0+192.gadedcb1 |
| codex-cli |
0.147.0 |
| Provider |
EXTERNAL_MODEL_PROVIDER_TYPE_AZURE_OPENAI MPS, target is an Azure deployment |
| Route |
POST {workspace}/ai-gateway/codex/v1/responses, wire_api = "responses" |
Root cause
Captured the request Codex actually sends by pointing model_providers.ucode-databricks.base_url at a local listener. The payload has no top-level tools; instead input[0] carries:
{"type": "namespace", "name": "functions", "description": ""}
Azure OpenAI rejects description: "". The gateway forwards the body verbatim, so it surfaces as MODEL_PROVIDER_ERROR.
Minimal proof it is this one field
Replayed the captured request against the same MPS twice, byte-identical except for that string:
| Request |
Result |
input[0].tools[0].description = "" (exactly what Codex sends) |
400 empty_string |
same request, description = "Shell and patch tools." |
200, normal completion |
A local proxy that fills only empty tool descriptions and forwards everything else untouched makes Codex work end-to-end against the Azure MPS, including tool calls (shell exec) and multi-turn.
Ruled out
- MCP servers —
-c mcp_servers={} fails identically; also reproduced with no MCP servers configured.
include_apply_patch_tool=false, tools.apply_patch=false, experimental_use_freeform_apply_patch=false, tools.view_image=false — all still 400.
- MPS
native_api_types — the Responses API works fine through this MPS; a hand-built Responses request with a valid tool returns 200.
- Not an auth or routing problem:
ucode auth-token works and the model resolves (model: <azure-deployment>, provider: ucode-databricks).
Suggested fix
The gateway is the natural place — it already knows the target is azure_openai, and normalising an empty description to a non-empty placeholder for Azure targets is a narrow, contained change. Alternatives are a fix in Codex (don't emit an empty description) or waiting on Azure to relax, neither of which ucode controls.
Filed separately: #324 (MPS model naming) and #325 (--dry-run side effects).
Summary
Every Codex request through an Azure OpenAI Model Provider Service fails with HTTP 400. Codex ≥ 0.147 packages its built-in tools as an input-item tool whose
descriptionis an empty string, and the Azure OpenAI Responses API rejects an emptydescriptionwhere OpenAI direct accepts it. Codex is unusable against an Azure OpenAI MPS as a result — the very first turn fails, with or without MCP servers.Versions
0.0.0+192.gadedcb10.147.0EXTERNAL_MODEL_PROVIDER_TYPE_AZURE_OPENAIMPS, target is an Azure deploymentPOST {workspace}/ai-gateway/codex/v1/responses,wire_api = "responses"Root cause
Captured the request Codex actually sends by pointing
model_providers.ucode-databricks.base_urlat a local listener. The payload has no top-leveltools; insteadinput[0]carries:{"type": "namespace", "name": "functions", "description": ""}Azure OpenAI rejects
description: "". The gateway forwards the body verbatim, so it surfaces asMODEL_PROVIDER_ERROR.Minimal proof it is this one field
Replayed the captured request against the same MPS twice, byte-identical except for that string:
input[0].tools[0].description = ""(exactly what Codex sends)empty_stringdescription = "Shell and patch tools."A local proxy that fills only empty tool descriptions and forwards everything else untouched makes Codex work end-to-end against the Azure MPS, including tool calls (shell exec) and multi-turn.
Ruled out
-c mcp_servers={}fails identically; also reproduced with no MCP servers configured.include_apply_patch_tool=false,tools.apply_patch=false,experimental_use_freeform_apply_patch=false,tools.view_image=false— all still 400.native_api_types— the Responses API works fine through this MPS; a hand-built Responses request with a valid tool returns 200.ucode auth-tokenworks and the model resolves (model: <azure-deployment>,provider: ucode-databricks).Suggested fix
The gateway is the natural place — it already knows the target is
azure_openai, and normalising an emptydescriptionto a non-empty placeholder for Azure targets is a narrow, contained change. Alternatives are a fix in Codex (don't emit an emptydescription) or waiting on Azure to relax, neither of which ucode controls.Filed separately: #324 (MPS model naming) and #325 (
--dry-runside effects).