Skip to content

[Bug]: microsoft-foundry reasoning models return 400 invalid_encrypted_content when continuing a thread #91033

Description

@chrisreddington

Bug type

Behavior bug (incorrect output/state without crash)

Beta release blocker

No

Summary

On the microsoft-foundry provider with a reasoning model, continuing a thread can fail with 400 invalid_encrypted_content. OpenClaw replays prior reasoning items with the encrypted_content blob but without the item id, and in our testing Foundry rejects the blob when the id is missing.

A fresh first turn has no history to replay, so it isn't affected; the failure shows up on a continued turn whose replayed history includes an encrypted reasoning item. A direct probe against the Foundry endpoint reproduces it deterministically for that request shape (id dropped, blob kept).

Steps to reproduce

  1. Configure the microsoft-foundry provider on api: "openai-responses" with a reasoning model (gpt-5.4 or gpt-5.4-mini), store: false, and include: ["reasoning.encrypted_content"].
  2. Start a thread and send a first message. It succeeds.
  3. Keep the thread going. Once the history contains an assistant turn that produced encrypted reasoning, the next request replays it.
  4. That continued turn 400s with the error below. The soonest this can happen is the second turn (if the first turn reasoned); it can take a few more turns if early turns produced no encrypted reasoning. Starting a fresh session clears it, and the same pattern can recur once that session replays an encrypted reasoning item.

Expected behavior

The continued turn succeeds, the same way a fresh first turn does. A fresh first turn has nothing to replay and isn't affected, which points at the replayed history rather than the model or the config.

Actual behavior

The continued turn fails with:

400 The encrypted content gAAA…== could not be verified.
Reason: Encrypted content could not be decrypted or parsed.   (code: invalid_encrypted_content)

OpenClaw version

2026.6.1 (2e08f0f)

Operating system

Ubuntu 26.04

Install method

npm global

Model

gpt-5.4 and gpt-5.4-mini (Azure AI Foundry reasoning models)

Provider / routing chain

openclaw -> microsoft-foundry (Azure AI Foundry, OpenAI-compatible Responses API)

Additional provider/model setup details

In stateless mode (store: false) the Responses API doesn't retain reasoning server-side on Microsoft Foundry, so to keep reasoning across turns you add reasoning.encrypted_content to include and replay the returned items on the next request. Each reasoning item comes back with both an id and an encrypted_content blob. (Azure AI Foundry docs:
https://learn.microsoft.com/azure/ai-foundry/openai/how-to/responses#encrypted-reasoning-items.)

Based on my configuration (and I haven't seen documented) Foundry needs the id present to accept the blob. When you keep the id, the same blob replays fine, but if you drop the id and keep the blob the request 400s (the probe below shows this directly).

For comparison, OpenAI's Responses guidance, in the function-calling flow, says to replay reasoning items "untouched" (https://platform.openai.com/docs/guides/reasoning#keeping-reasoning-items-in-context); stripping the id diverges from that.

Logs, screenshots, and evidence

I pulled 47 real reasoning items from a session running encrypted reasoning replay and posted them straight at the Foundry Responses endpoint in a few shapes:

| what I sent | `id` | `encrypted_content` | result |
| --- | --- | --- | --- |
| all 47 items, untouched | kept | kept | 200 |
| all 47 items | dropped | kept | **400** (same error, same blob) |
| all 47 items | dropped | dropped | 200 |
| a single item | kept | kept | 200 |

The second row is what OpenClaw sends today. The first row is the key one: the same blob replays cleanly when its id is still attached, so for this request the rejection tracks the missing id rather than an expired blob or a rotated key. The failure tracks one thing, whether the id and the blob are sent together.

Impact and severity

  • Affected: anyone on the microsoft-foundry provider running a reasoning model with encrypted reasoning replay.
  • Severity: High. Once it triggers, the thread can't continue, and the only recovery is /new, which throws away the conversation.
  • Frequency: deterministic for the rejected request shape, as the probe shows (id dropped, blob kept). In a live thread it surfaces on a continued turn that replays an encrypted reasoning item.
  • Consequence: multi-turn conversations on Foundry reasoning models can break partway through.

Additional information

Where it lives. Paths below are repo source at tag v2026.6.1 (the running build ships these bundled under dist/).

On store: false, OpenClaw replays previous reasoning items into the next request's input. In src/llm/providers/openai-responses-shared.ts:

// normalizeResponsesReasoningReplayItem, ~line 46
const next = { ...params.item };
if (!Array.isArray(next.summary)) next.summary = [];
if (!params.replayResponsesItemIds) {
	delete next.id;          // ~line 55: id goes
}
return next;                 // encrypted_content stays
  • normalizeResponsesReasoningReplayItem (~line 46) builds the replay item and drops the id when replayResponsesItemIds is false.
  • convertResponsesMessages (exported ~line 168) reconstructs each reasoning item from the assistant block's thinkingSignature (JSON.parse(block.thinkingSignature), ~line 268) and pushes it into the request input (~line 271).
  • The Responses provider (src/llm/providers/openai-responses.ts, ~line 196) calls that converter with replayResponsesItemIds: options?.replayResponsesItemIds ?? false, so the default for this path is false.

So every replayed reasoning item goes out with no id and the blob intact, i.e. { "type": "reasoning", "summary": [ … ], "encrypted_content": "gAAA…" }.

There's a second convertResponsesMessages in src/agents/openai-transport-stream.ts (~line 1040), but it's only selected when hasOpenClawTransportRequirement is true, i.e. when the model has request.proxy, request.tls, or localService set (src/agents/provider-transport-stream.ts, ~line 97). A normal Foundry provider doesn't exercise it, so looks like the shared-module copy is the live path here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    P1High-priority user-facing bug, regression, or broken workflow.bugSomething isn't workingbug:behaviorIncorrect behavior without a crashclawsweeper:needs-live-reproClawSweeper needs live local, crabbox, or manual validation to confirm this issue.clawsweeper:needs-maintainer-reviewClawSweeper marked this issue as needing maintainer review before automation.clawsweeper:no-new-fix-prClawSweeper does not recommend queueing a new automated fix PR for this issue.impact:auth-providerAuth, provider routing, model choice, or SecretRef resolution may break.impact:session-stateSession, memory, transcript, context, or agent state can drift or corrupt.issue-rating: 🐚 platinum hermitGood issue quality with a plausible reproduction path needing some confirmation.

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions