Azure Foundry tool sessions still fail on 0.118.0 without local payload patching
What version of Codex CLI is running?
codex-cli 0.118.0
What subscription do you have?
Azure Foundry / Azure OpenAI only
Which model were you using?
gpt-5.4 via Azure Foundry
What platform is your computer?
macOS arm64
What terminal emulator and version are you using (if applicable)?
zsh on macOS
What issue are you seeing?
Azure-backed Codex CLI tool sessions are still broken on 0.118.0.
I have a local shell alias named codex-azure, but that alias is only:
codex --profile azure_foundry
So this is not an alias bug. The underlying Azure profile itself is broken for tool-driven sessions.
With direct Azure Foundry configuration, Codex can start a session and even run the first tool call, but the follow-up /responses request fails with Azure:
{
"error": {
"code": "invalid_payload",
"message": "The provided data does not match the expected schema",
"param": "/",
"type": "invalid_request_error",
"details": [],
"additionalInfo": {
"request_id": "22d9b916fade3079190d29139a72da64"
}
}
}
In practice, Azure/Codex is not usable for normal interactive repo work unless I put a local patching proxy in front of Azure and mutate the payloads before they go upstream.
That proxy is only a workaround. Without patching, Azure tool use is still broken.
This looks related to #15584 and #14695, but it is still reproducible on 0.118.0.
What steps can reproduce the bug?
- Configure Codex CLI to use Azure Foundry / Azure OpenAI with
wire_api = "responses". - Run Codex with that Azure profile.
- Ask for anything that triggers a shell tool call.
Minimal repro:
codex --profile azure_foundry exec "Run git status --short --branch and summarize the repo state in two sentences."
Equivalent local alias repro:
codex-azure exec "Run git status --short --branch and summarize the repo state in two sentences."
Observed behavior:
- the shell tool itself runs
- then the next Azure
/responsesrequest fails withinvalid_payload - interactive tool-driven work cannot continue
I also reproduced it in interactive mode by starting Codex and asking it to inspect a repo. As soon as the first tool-backed turn completes, Azure returns schema/payload errors.
What is the expected behavior?
Azure Foundry should accept the tool round-trip and allow Codex to continue the session normally, without requiring a user-side proxy that patches or strips malformed payload items.
Additional information
Relevant local setup details:
codex-azureis just an alias/wrapper forcodex --profile azure_foundry- the bug reproduces with the underlying profile directly, so the alias is not the cause
- direct Azure Foundry endpoint: broken after tool call
- local payload-patching proxy: can partially mask the issue, which strongly suggests Codex is generating Azure-incompatible
/responsescontinuation payloads
This means Azure support is effectively not working out of the box right now for normal tool-using Codex sessions unless users patch around it locally.
11 Comments
Potential duplicates detected. Please review them and close your issue if it is a duplicate.
Powered by Codex Action
Thanks for the bug report. Do you have reason to believe that this is a Codex client issue? If so, was it working with a previous version of the client? For example, if you downgrade to 0.116.0, does it work?
Based on an initial read of your report, I suspect this might be an Azure service issue. If you haven't already done so, I recommend reporting this to the folks at Azure. If this is an issue on their end, they will not know about it unless you report it.
Seeing the same error on my end.
---
What version of Codex CLI is running?
codex-cli 0.118.0What subscription do you have?
Azure Foundry / Azure OpenAI only
Which model were you using?
gpt-5.3-codex via Azure Foundry
What platform is your computer?
windows x64
What terminal emulator and version are you using (if applicable)?
bash on WSL
---
Here is one conversation:
Some additional info:
codex-cli 0.116.0.Same error here. But I found azure gpt is working well in opencode. It doesn't look like an Azure issue?
We traced this to three Codex-side incompatibilities with Azure Responses API:
Maybe I can submit a PR to fix this issue.
I dug into this more and got Azure tool sessions working with a small compatibility layer. The main fixes were:
store = falseprevious_response_idand send a stateless replay shapeassistantorreasoningitems back to Azuredeveloper/system/usermessagesfunction_callfunction_call_outputfunctiontools always include a validparametersschema objectThe most important finding was that the same failing Azure continuation request passed once the replayed
assistant+reasoningitems were removed. So the main incompatibility appears to be the follow-up replay payload shape Codex sends toAzure.
This is the compatibility/mutation layer, I can also provide the full proxy wrapper if anyone wants it.
```js
function isRecord(value) {
return value !== null && typeof value === "object" && !Array.isArray(value);
}
function stringifyField(value) {
return typeof value === "string" ? value : JSON.stringify(value);
}
function isToolOutputItem(item) {
return isRecord(item) && item.type === "function_call_output";
}
function defaultToolParameters(tool) {
if (isRecord(tool.parameters)) return tool.parameters;
if (isRecord(tool.input_schema)) return tool.input_schema;
if (isRecord(tool.schema)) return tool.schema;
return { type: "object", properties: {}, additionalProperties: true };
}
function normalizeMessageContent(content) {
if (typeof content === "string") return content;
if (Array.isArray(content)) return content;
return content ?? [];
}
function normalizeConversationItem(item) {
const type =
typeof item.type === "string" ? item.type : typeof item.role === "string" ? "message" : null;
if (!type) return null;
switch (type) {
case "message":
case "output_message":
if (typeof item.role !== "string" || item.role.length === 0) return null;
return {
type: "message",
role: item.role,
content: normalizeMessageContent(item.content),
};
case "reasoning": {
const normalized = { type: "reasoning" };
if (typeof item.id === "string" && item.id.length > 0) normalized.id = item.id;
if (Array.isArray(item.summary)) normalized.summary = item.summary;
if (Array.isArray(item.content) && item.content.length > 0) normalized.content = item.content;
if (typeof item.encrypted_content === "string" && item.encrypted_content.length > 0) {
normalized.encrypted_content = item.encrypted_content;
}
if (!("summary" in normalized) && !("encrypted_content" in normalized)) return null;
return normalized;
}
case "function_call":
return item.call_id && item.name && item.arguments != null
? {
type: "function_call",
call_id: item.call_id,
name: item.name,
arguments: stringifyField(item.arguments),
}
: null;
case "function_call_output":
return item.call_id && item.output != null
? {
type: "function_call_output",
call_id: item.call_id,
output: stringifyField(item.output),
}
: null;
default: {
const passthrough = { ...item };
delete passthrough.id;
delete passthrough.status;
delete passthrough.created_by;
if (passthrough.content === null) delete passthrough.content;
return passthrough;
}
}
}
function normalizeAzureResponsesInput(body) {
if (!Array.isArray(body.input)) return false;
let patched = false;
const normalizedInput = [];
for (const item of body.input) {
if (!isRecord(item)) {
normalizedInput.push(item);
continue;
}
const normalized = normalizeConversationItem(item);
if (normalized == null) {
patched = true;
continue;
}
if (JSON.stringify(item) !== JSON.stringify(normalized)) {
patched = true;
}
normalizedInput.push(normalized);
}
if (patched) body.input = normalizedInput;
return patched;
}
function normalizeAzureResponsesTools(body) {
if (!Array.isArray(body.tools)) return false;
let patched = false;
body.tools = body.tools.map((tool) => {
if (!isRecord(tool)) return tool;
const nextTool = { ...tool };
if (nextTool.type === "function" && !isRecord(nextTool.parameters)) {
nextTool.parameters = defaultToolParameters(nextTool);
patched = true;
}
return nextTool;
});
return patched;
}
function hasReplayContextItems(input) {
return Array.isArray(input) && input.some((item) => isRecord(item) && !isToolOutputItem(item));
}
function pruneAzureToolFollowUpInput(body) {
if (!Array.isArray(body.input)) return false;
if (!body.input.some(isToolOutputItem)) return false;
const before = body.input.length;
body.input = body.input.filter((item) => {
if (!isRecord(item) || typeof item.type !== "string") return false;
if (item.type === "message") {
return item.role === "developer" || item.role === "system" || item.role === "user";
}
return item.type === "function_call" || item.type === "function_call_output";
});
return body.input.length !== before;
}
function patchAzureResponsesRequest(body) {
if (!isRecord(body)) return false;
let patched = false;
if (body.store === true) {
body.store = false;
patched = true;
}
if (
body.store === false &&
typeof body.previous_response_id === "string" &&
body.previous_response_id.length > 0 &&
hasReplayContextItems(body.input)
) {
delete body.previous_response_id;
patched = true;
}
patched = normalizeAzureResponsesTools(body) || patched;
patched = normalizeAzureResponsesInput(body) || patched;
patched = pruneAzureToolFollowUpInput(body) || patched;
return patched;
}
@minimAluminiumalism I had to patch the payload for opencode in a similar way as above.
It seems that I cannot create a PR to this repo directly. https://github.com/minimAluminiumalism/codex/commit/2d9909cc12a6651e4db57714737876239af96363
@Alfredo-Sandoval Please check this fix, maybe it's a solution? CC @bolinfest
@minimAluminiumalism let me build codex with these fixes and test them out.
@etraut-openai is this worth a PR? or do you guys prefer this fixed on the Azure side?
@Alfredo-Sandoval, this should be fixed on the Azure side. We don't want to maintain workarounds for Azure in the Codex harness.
If you're hitting this error, the cause is likely your base_url in
~/.codex/config.toml.The endpoint shown on the Azure AI Foundry project overview page doesn't fully support the responses wire API:
Foundry project endpoint - breaks after first tool call
base_url = "https://UNIQUE-PROJECT-NAME.services.ai.azure.com/api/projects/UNIQUE-PROJECT-NAME_project/openai/v1"Switching to the classic Azure OpenAI resource endpoint fixes it:
Classic Azure OpenAI endpoint - works correctly
base_url = "https://UNIQUE-PROJECT-NAME.openai.azure.com/openai/v1"You can find this URL on the same project overview page under Libraries → Azure OpenAI (it defaults to showing the Microsoft Foundry tab, which is the broken one).
Hope this saves someone some time!