Azure Foundry tool sessions still fail on 0.118.0 without local payload patching

Resolved 💬 11 comments Opened Apr 6, 2026 by Alfredo-Sandoval Closed Apr 8, 2026
💡 Likely answer: A maintainer (github-actions[bot], contributor) responded on this thread — see the highlighted reply below.

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?

  1. Configure Codex CLI to use Azure Foundry / Azure OpenAI with wire_api = "responses".
  2. Run Codex with that Azure profile.
  3. 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 /responses request fails with invalid_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-azure is just an alias/wrapper for codex --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 /responses continuation 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.

View original on GitHub ↗

11 Comments

github-actions[bot] contributor · 3 months ago

Potential duplicates detected. Please review them and close your issue if it is a duplicate.

  • #15584

Powered by Codex Action

etraut-openai contributor · 3 months ago

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.

ajboloor · 3 months ago

Seeing the same error on my end.

---

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.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:

› hello
 Hi. What do you want to work on in this repo?


› what is the weather in SFO?


• Searching the web

• Searched weather: San Francisco, CA

────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────

• For San Francisco (SFO area) today, Monday, April 6, 2026: it’s around 58°F now, with a day range near 60°F / 50°F and little to no rain expected. (a
  .msn.com (https://a.msn.com/54/en-us/ct37.780%2C-122.420))

  If you want, I can give the airport-specific SFO METAR/TAF conditions next.


› what is this repo?


■ {
  "error": {
    "code": "invalid_payload",
    "message": "The provided data does not match the expected schema",
    "param": "/",
    "type": "invalid_request_error",
    "details": [],
    "additionalInfo": {
      "request_id": "7a57e9927fca7fd75a4328c3adb45041"
    }
  }
}

> hello
■ {
  "error": {
    "code": "invalid_payload",
    "message": "The provided data does not match the expected schema",
    "param": "/",
    "type": "invalid_request_error",
    "details": [],
    "additionalInfo": {
      "request_id": "722b5ec74bbfa9fdc25388204c957be7"
    }
  }
}

Some additional info:

  • The failure seems to begin during particular tool calls. It is interesting that once the failure gets triggered when a coding based tool is called, it will continue to fail even when there is no tool call.
  • Same issue on codex-cli 0.116.0.
  • I was following the instructions from https://learn.microsoft.com/en-us/azure/foundry/openai/how-to/codex
  • Restarting the codex session resolves the issue until a particular tool call is invoked.
minimAluminiumalism · 3 months ago

Same error here. But I found azure gpt is working well in opencode. It doesn't look like an Azure issue?

minimAluminiumalism · 3 months ago

We traced this to three Codex-side incompatibilities with Azure Responses API:

  1. store: true — Codex forces this for Azure providers, but Azure’s stored item mechanism breaks multi-turn after ~5 turns. @ai-sdk/openai defaults to store: false, sending full conversation history each request. With store: false, we verified 14+ turns with no issues on the same Azure endpoint.
  1. "content": null in reasoning items — Azure rejects null (expects field omitted). Codex serializes Option<Vec<...>> None as null.
  1. "type": "custom" tool — Azure doesn’t support it. Needs to be "function".

Maybe I can submit a PR to fix this issue.

Alfredo-Sandoval · 3 months ago

I dug into this more and got Azure tool sessions working with a small compatibility layer. The main fixes were:

  • force store = false
  • if replay context is present, drop previous_response_id and send a stateless replay shape
  • on tool follow-up turns, do not replay assistant or reasoning items back to Azure
  • only replay:
  • developer / system / user messages
  • function_call
  • function_call_output
  • ensure function tools always include a valid parameters schema object
  • strip malformed / null-only reasoning content before forwarding

The most important finding was that the same failing Azure continuation request passed once the replayed assistant +
reasoning items were removed. So the main incompatibility appears to be the follow-up replay payload shape Codex sends to
Azure.

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;
}

Alfredo-Sandoval · 3 months ago
Same error here. But I found azure gpt is working well in opencode. It doesn't look like an Azure issue?

@minimAluminiumalism I had to patch the payload for opencode in a similar way as above.

minimAluminiumalism · 3 months ago

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

Alfredo-Sandoval · 3 months ago

@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?

etraut-openai contributor · 3 months ago

@Alfredo-Sandoval, this should be fixed on the Azure side. We don't want to maintain workarounds for Azure in the Codex harness.

jhubbardsf · 2 months ago

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!