0.147.0 regression: Azure Responses rejects empty functions namespace description
What version of Codex CLI is running?
codex-cli 0.147.0
What subscription do you have?
Azure OpenAI through a custom Responses provider (routed through Azure API Management).
Which model were you using?
gpt-5.6-sol
What platform is your computer?
Microsoft Windows NT 10.0.26200.0 x64
What terminal emulator and version are you using (if applicable)?
VS Code integrated terminal / PowerShell.
Codex doctor report
Not included because the issue is reproducible from the serialized HTTP payload and contains no authentication or connectivity failure.
What issue are you seeing?
Codex CLI 0.147.0 fails before the model can answer when using an Azure OpenAI Responses provider:
{
"error": {
"message": "Invalid 'input[0].tools[0].description': empty string. Expected a string with minimum length 1, but got an empty string instead.",
"type": "invalid_request_error",
"param": "input[0].tools[0].description",
"code": "empty_string"
}
}
The serialized request contains this first input item:
{
"type": "additional_tools",
"role": "developer",
"tools": [
{
"type": "namespace",
"name": "functions",
"description": "",
"tools": [
{
"type": "custom",
"name": "exec",
"description": "Run JavaScript code to orchestrate/compose tool calls..."
}
]
}
]
}
Azure's NamespaceToolParam schema requires description and specifies minLength: 1:
https://learn.microsoft.com/rest/api/microsoft-foundry/azureopenai/responses#openainamespacetoolparam
This is a regression from 0.146.0. With the same provider, model, authentication, and prompt, 0.146.0 sends the exec custom tool directly without the empty functions namespace and succeeds.
Disabling only code_mode_host does not work around the issue in 0.147.0; the empty namespace remains in the serialized request.
What steps can reproduce the bug?
Configure a custom Azure OpenAI Responses provider:
model_provider = "azure"
model = "gpt-5.6-sol"
[model_providers.azure]
name = "Azure OpenAI"
base_url = "https://<azure-or-gateway-endpoint>/openai/v1"
wire_api = "responses"
Then run:
codex exec --ephemeral --skip-git-repo-check "Reply with exactly: OK"
Observed comparison:
0.146.0: succeeds and returnsOK.0.147.0: returns theinput[0].tools[0].descriptionvalidation error above.
I captured the failing 0.147.0 request and replayed it against the same endpoint, changing only:
"description": "Default Codex tools."
The otherwise identical request returned HTTP 200. This confirms the empty namespace description is the direct cause.
The regression appears related to #37022, which grouped default tools under the functions namespace:
https://github.com/openai/codex/pull/37022
What is the expected behavior?
Codex should serialize the default functions namespace with a non-empty description, for example:
"description": "Default Codex tools."
Alternatively, it should avoid the namespace wrapper for providers that do not advertise support for that request shape.
Additional information
Related provider compatibility reports:
- #31875
- #31882
- #32318
This report is narrower: it specifically covers the 0.146.0 to 0.147.0 regression introduced by serializing the new functions namespace with an empty required description.
26 Comments
This happens 100% of the time on 0.147.0 if you're using Azure OpenAI. To fix, I downgraded to 0.146.1.
Independent reproduction on macOS 26.6 (arm64) with
codex-cli 0.147.0and a custom OpenAI-compatible Responses provider. This does not appear to be Windows- or Azure-specific.The same endpoint, authentication, Responses wire protocol, and model configuration worked with 0.146.1. After upgrading to 0.147.0, the first request fails before model output with the same validation error:
There is also a useful model-catalog signal:
"tool_mode": nulland"use_responses_lite": falseto every available model makes every model work again.wire_api = "responses", the endpoint, and credentials remain unchanged.That makes the failure follow the per-model Responses Lite / code-mode metadata rather than authentication or ordinary Responses routing.
Current
mainstill deliberately returns an empty string for the defaultfunctionsnamespace indefault_namespace_description(), andresponses_lite_preserves_empty_functions_namespace_descriptionasserts that wire value. This seems inconsistent with the earlier rationale in #17946, which established that namespace descriptions need a non-empty fallback because Responses-compatible endpoints rejectdescription: "".A minimal fix would be to keep the new namespace grouping while giving the default namespace a non-empty fallback, for example:
and update the Responses Lite regression test to assert the non-empty description.
I would be happy to prepare the focused code change and tests if a maintainer would like to invite an external PR.
I believe this should be fixed in the Responses request construction rather than through per-model configuration.
### Root cause
In Codex 0.147.0, the Responses Lite path generates an empty description for the default
functionsnamespace:```rust
pub fn default_namespace_description(namespace_name: &str) -> String {
if namespace_name == DEFAULT_FUNCTION_NAMESPACE {
String::new()
} else {
format!("Tools in the {namespace_name} namespace.")
}
}
This causes Codex to serialize a tool with:
{
"description": ""
}
A Responses-compatible endpoint can reject this request because tool descriptions must be non-empty:
Invalid 'input[0].tools[0].description': empty string.
Expected a string with minimum length 1.
This is provider- and model-independent: any model using this Responses Lite serialization path can be affected.
### Proposed fix
Always generate a non-empty namespace description, including for the default functions namespace:
pub fn default_namespace_description(namespace_name: &str) -> String {
format!("Tools in the {namespace_name} namespace.")
}
Alternatively, the default namespace can return a dedicated description:
pub fn default_namespace_description(namespace_name: &str) -> String {
if namespace_name == DEFAULT_FUNCTION_NAMESPACE {
"Tools in the functions namespace.".to_string()
} else {
format!("Tools in the {namespace_name} namespace.")
}
}
It would also be useful to add a defensive check at the Responses serialization boundary so that Codex never emits a
tool whose description is empty or whitespace-only.
### Regression test
The existing test that expects the default namespace description to remain empty should be changed to expect a non-
empty value.
A request-level regression test should also:
assert!(!description.trim().is_empty());
### Temporary workaround
Disabling the Responses Lite tool path through model metadata avoids the invalid payload:
{
"tool_mode": null,
"use_responses_lite": false
}
However, this must be added to every affected model entry, so it is only a configuration workaround. Fixing the
default namespace description would solve the issue for all models and all OpenAI-compatible Responses providers.
As a temporary workaround, I kept the provider on the Responses API and supplied a custom model catalog:
```toml
wire_api = "responses"
model_catalog_json = "/path/to/model-catalog.json"
In model-catalog.json, I added the following fields to every affected model entry:
{
"tool_mode": null,
"use_responses_lite": false
}
This prevents Codex from using the Responses Lite namespace serialization path that produces the empty tool
description. It does not switch the provider to Chat Completions and tool calling continues to work.
These fields must be added to every selectable model, not only the default model. Otherwise, switching to another
model can trigger the same 400 error again.
This is only a temporary workaround because these model-catalog fields appear to be internal and are not documented as
stable public configuration options.
This is the Nth time that they release a tool that's broken.
If you're installing over the sh script directly from OpenAI you can easily revert to the previous version. Then when it restarts just skip updates until the next version where it's hopefully fixed - fingers crossed!
CODEX_NON_INTERACTIVE=1 sh -c "$(curl -fsSL https://chatgpt.com/codex/install.sh)" -- --release 0.146.0Or equivalently, with the env var:
CODEX_RELEASE=0.146.0 sh -c "$(curl -fsSL https://chatgpt.com/codex/install.sh)"If you haven't already done so, please report the problem to Azure. It sounds like their responses endpoint implementation is out of date. They periodically sync their implementation with OpenAI's.
Duplicate with #37425
Additional data point for whoever picks up the patch: omitting the field is not a valid fix.
The obvious patch —
#[serde(skip_serializing_if = "String::is_empty")]on the namespacedescription— would trade one 400 for another.descriptionis required onNamespaceToolParam, so dropping it fails validation too.Verified against a live Azure-backed Responses endpoint, same minimal payload each time, only
input[0].tools[0].descriptionvarying:|
description| Result || --- | --- |
|
""(what 0.147.0 sends) | 400empty_string|| key omitted | 400
missing_required_parameter|| any non-empty string | 200 OK |
So
default_namespace_description()needs to return a non-empty string forDEFAULT_FUNCTION_NAMESPACE— the existingformat!("Tools in the {namespace_name} namespace.")branch works as-is.Minimal repro, no Codex required:
Change
"description": ""to any non-empty string and the identical request returns 200.For completeness, the captured payload diff on macOS 15 (arm64), same config, provider and prompt, model
gpt-5.6-sol, captured by pointingbase_urlat a local sink:0.146.1and0.145.0—input[0](additional_tools) carries four tools flat, all descriptions populated:0.147.0— the same tools are wrapped in afunctionsnamespace whose description is empty:That empty string is the only one in the entire ~63 KB request body, so the blast radius of the fix is a single field.
Confirming @lql341's analysis and proposed fix. I applied the same minimal change locally and updated the focused tool serialization and Responses Lite request tests. Both pass:
I also built the patched CLI and tested it against a live Azure OpenAI Responses endpoint using
gpt-5.6-sol. With the same provider configuration and prompt, the official 0.147.0 binary fails withempty_string, while the patched binary completes successfully and returnsOK.I have not opened a PR because the repository requires an explicit invitation. If useful, may I be invited to submit the focused patch, or would the maintainers prefer to apply it internally?
against rust 0.147.0 tag fixed it for me.
Still present in
0.148.0-alpha.6(published 2026-08-10), so the fix has not landed on the 0.148 line either.Captured the serialized request from the alpha binary against a local capture endpoint. macOS 26.5 arm64, custom Microsoft Foundry Responses provider,
gpt-5.6-sol:default_namespace_description()is unchanged, so anyone on a non-ChatGPT Responses backend will hit this again on the next stable release unless the one-line fix in this thread lands first.Scope, from the same endpoint
Affected:
gpt-5.6-sol,gpt-5.6-luna,gpt-5.6-terra. All three serialize tools as anadditional_toolsinput item with the emptyfunctionsnamespace.Unaffected:
gpt-5.5,gpt-5.4,gpt-5.3-codex,gpt-5.2-codex,gpt-5.1-codex-max. These send a standard top-leveltoolsarray with no namespaces at all.Exactly one empty description per request, always
input[0].tools[0].Workaround: patch in flight instead of downgrading
I pointed the provider
base_urlat a localhost shim that fills any emptydescriptionand forwards everything else untouched:The only non-obvious requirement is that the shim has to stream the SSE response through as it arrives rather than buffering it, otherwise output arrives in one lump at the end of the turn. Verified end to end on 0.147.0 with multi-turn shell tool calls at
model_reasoning_effort = "max".Why not the other two workarounds
Downgrading to 0.146.1 gives up a release, and with 0.148 alphas still affected it is an open-ended hold rather than a short one.
model_catalog_jsonwithuse_responses_lite: falseworks by disabling Responses Lite, which costs the code-modeexectool. For 5.6 specifically that is much of the reason to be on the model. It also has to be repeated for every selectable model, and those fields are undocumented internals.The shim keeps 5.6 behavior fully intact, needs no change to Codex, and is self-retiring: it logs how many descriptions it patched per request, so
patched 0on every request is the signal that the upstream fix has landed and it can be deleted.Happy to keep testing alphas against a live Foundry endpoint if that is useful to whoever picks this up.
Follow-up with schema evidence, since it explains why this only reproduces on Azure and suggests both sides have something to change.
Sending
"description": ""is valid against OpenAI's own published schema. From openai/openai-openapi, componentNamespaceToolParam(identical inBetaNamespaceToolParam):descriptionis required but unconstrained in length, whilenameimmediately above it carries an explicitminLength: 1. So0.147.0is emitting a technically spec-conformant request.Azure adds the constraint. Their published spec is derived from the same source and is otherwise identical, including the doc strings, the
requiredlist,minLength: 1onname, andminItems: 1ontools. It differs by exactly one line (microsoft-foundry-openapi3.yaml, v1, L62594-62597):I have filed that against the spec as Azure/azure-rest-api-specs#45388.
That makes @etraut-openai's suggestion to report this to Azure the right call, and I have done so. Two notes for whoever picks this up here, though:
additional_toolsandnamespacecorrectly, and the same request returns 200 with any non-empty description. Only this one field differs.default_namespace_description()proposed earlier in this thread unblocks every affected user immediately, and sending a real description is better for the model regardless of which schema is treated as authoritative.Confirming again that this still reproduces on
0.148.0-alpha.6.@LifeBringer are you willing to post that full shim you're using?
Independent reproduction confirming the root cause described above (functions namespace description empty string) against a custom OpenAI-compatible Responses provider proxying to an internal Azure OpenAI Foundry deployment.
Environment: Windows,
codex-cli 0.147.0, custommodel_provider(wire_api = "responses"), model aliased to thegpt-5.6-terracatalog entry.Error observed (deterministic, 100% reproducible, same on every retry):
Captured the actual request payload via a local logging reverse proxy (custom
model_providerpointed at127.0.0.1) to confirmtools[0]is exactly the"namespace"/"functions"wrapper object withdescription: "", matching what's described above.One additional data point nobody's posted yet: tried
codex exec --disable code_mode_host --disable code_modeas a hoped-for client-side workaround. This does not avoid the bug — Codex printsCode Mode is unavailable because code-mode host is disabled. Code mode will fail closed; enable features.code_mode_host and install codex-code-mode-host, but still serializes and sends the exact same empty-descriptionfunctionsnamespace object regardless of the feature flag state. So disabling the feature only breaks Code Mode's own tool execution, without avoiding the malformed request.Workaround that does work (per the
tool_mode/use_responses_litefix posted above): pulled the built-in catalog viacodex debug models, set"tool_mode": null, "use_responses_lite": falseon the three affectedgpt-5.6-*entries, saved as a custom file, and pointedmodel_catalog_jsonat it inconfig.toml. Confirmed working end-to-end on a real request that previously failed 100% of the time.+1 on the proposed
default_namespace_descriptionfix — happy to test a build if one gets cut.here is a workaround for downgrading the codex cli version, the key is
CODEX_RELEASE=0.146.1:If Azure / Foundry Responses rejects 0.147.0 with empty
functionsnamespace description, pin back to 0.146.1. That build does not wrap exec in an empty namespace.Method:
CODEX_NON_INTERACTIVE=1 CODEX_RELEASE=0.146.1againsthttps://chatgpt.com/codex/install.sh(Windows:npm install -g @openai/codex@0.146.1).codex --versionreports 0.146.1.Evidence: 0.147.0 serializes
tools[0]as afunctionsnamespace with"description": "". Azure's NamespaceToolParam requires minLength 1. Thread confirmations include the official installer pin to 0.146.1.Independent community workaround; not an official OpenAI fix.
is there a workaround for the chatgpt app? I'm running into the same issue there, but don't see a way to downgrade app versions
I got codex cli 0.148.0 today. The problem is still there.
functionsnamespace is serialized with"description": "", which strict Responses API backends (Azure) reject with 400Status: still reproducing on
main@f5a3dc5and on0.148.0-alpha.15.TL;DR
The default
functionsnamespace is special-cased to an empty description, and the field is a non-optionalStringwith no skip attribute — so every request carries"description": "", which Azure-backed Responses endpoints reject asempty_string.Root cause
<!-- codex-rs/tools/src/responses_api.rs:64-70 -->
Every other namespace (
collaboration,web,skills,memory,image_gen) gets a real description from this same helper; onlyfunctionsgets"".Why the empty value reaches the wire
So the empty string is always serialized rather than omitted.
Observed request payload
Captured with a local forwarding proxy.
input[0]is anadditional_toolsitem:All 9 inner tools have proper descriptions (
execis 10,495 chars) — only the outerfunctionscontainer is empty.Error returned by the gateway:
Suggested fix
Option A — drop the special case (smallest diff):
Two things suggest this doesn't break semantics:
``
rust
``// codex-rs/tools/src/tool_spec.rs:118-120
ToolSpec::Namespace(namespace) if namespace.name == DEFAULT_FUNCTION_NAMESPACE => {
if !namespace.description.trim().is_empty() {
functions.description = namespace.description.clone();
}
Option B — make the field optional, if the empty description is deliberate (i.e. you don't want filler text for the default namespace):
This omits the key instead of sending an empty value, preserving current prompt semantics for existing backends.
Tests that would need updating either way:
codex-rs/tools/src/tool_spec_tests.rs:235—assert_eq!(tools[0]["description"], "");codex-rs/core/tests/suite/responses_lite.rs:144—assert_eq!(functions_namespaces[0]["description"], "");<details>
<summary><b>No client-side workaround exists — 16 configurations verified against a mock server</b></summary>
Verified by capturing the outgoing request body against a local mock server (zero API cost):
| Attempted workaround | Result |
| --- | --- |
|
code_mode/code_mode_host/code_mode_only = false| empty description still sent ||
collaboration_modes = false| still sent ||
multi_agent/multi_agent_v2 = false| still sent ||
include_collaboration_mode_instructions = false| still sent ||
tool_search/deferred_tool_world_state = false| still sent ||
js_repl/js_repl_tools_only = false| still sent ||
experimental_supported_tools = []| still sent ||
[tools.update_plan] enabled = false| still sent || All of the above combined | still sent |
| Non-GPT-5.6 model (
gpt-4.1) | still sent || Empty
CODEX_HOME(no plugins/skills/MCP) | still sent |The
functionsnamespace container is emitted unconditionally and is not gated behind any feature flag.</details>
Interacting constraint (worth flagging separately)
On the same gateway, dropping the
x-context-provider: Azureheader routes to a different backend that accepts the empty description but rejectsreasoning.context:Codex sends
reasoning: { effort, context }unconditionally for GPT-5.6-family models, and removingmodel_reasoning_effortfrom config does not stop it. So neither route is usable from the client side today. Happy to file that as a separate issue if you'd prefer.The CLI pin does not carry over to the ChatGPT desktop app. I do not know of a supported app-version downgrade or a confirmed app-side switch that removes this empty namespace.
For now:
tools[0].descriptionas an empty string, the bundled client still needs the upstream serialization fix.Limitation: disabling only
code_mode_hostwas already reported not to remove the empty namespace, so this is a CLI fallback rather than a desktop-app fix.Today it seems that Azure have changed something. Codex >= 0.147.0 is now compatible with it.
Have u updated to 0.149?
Kinda too scared it will explode
is this fixed now?
Confirmed fixed using 0.149 on Linux
Update: several users now report that Azure/Foundry compatibility is restored in Codex 0.149, including a confirmation on Linux.
If you use the affected Azure Responses profile:
codex --version.functionsnamespace description error is gone.The earlier 0.146.1 pin is now only a fallback for users who still reproduce the error; it should not be treated as the preferred path when 0.149 works.