Reasoning+message items must appear as consecutive pairs in input, but nothing documents this. The most common pattern — filtering response.output to keep only messages — silently produces orphaned items → 400 on the next turn.
This broke OpenClaw (64.9k forks) on gpt-5.3-codex. They had to add downgradeOpenAIReasoningBlocks() to strip orphan reasoning items.
400: Item 'msg_...' of type 'message' was provided without its required preceding item of type 'reasoning'
Tested in .NET, JS, Python, Go, Java — identical results across all 5 SDKs. This is an API-level constraint, not SDK-specific. Confirmed via curl (see gist). The SDK types don't prevent building input arrays that violate it.
The .NET SDK uses shared ResponseItem types for both input and output — output items pass back as input without compile errors, which makes the broken pattern especially easy to write.
| Model |
A (all items) |
B (msgs only) |
| gpt-5.3-codex (reasoning=high) |
PASS |
FAIL |
| o4-mini |
PASS |
FAIL* |
* Nondeterministic — o4-mini sometimes returns reasoning-only output (no message to orphan). Codex with reasoning=high reliably returns both items.
Workaround: PreviousResponseId. For manual history, always pass reasoning+message pairs together.
Related:
To Reproduce
using System.ClientModel;
using OpenAI;
using OpenAI.Responses;
#pragma warning disable OPENAI001
var client = new ResponsesClient(
new ApiKeyCredential(Environment.GetEnvironmentVariable("OPENAI_API_KEY")!));
var prompts = new[] { "Write a Python prime checker.", "Add type hints.", "Add docstrings." };
var conversation = new List<ResponseItem>();
for (int i = 0; i < prompts.Length; i++)
{
Console.WriteLine($"\n> {prompts[i]}");
conversation.Add(ResponseItem.CreateUserMessageItem(prompts[i]));
var options = new CreateResponseOptions("gpt-5.3-codex", conversation)
{
MaxOutputTokenCount = 300,
ReasoningOptions = new ResponseReasoningOptions { ReasoningEffortLevel = ResponseReasoningEffortLevel.High },
};
try
{
var response = await client.CreateResponseAsync(options);
Console.WriteLine($" {response.Value.GetOutputText()?[..Math.Min(80, response.Value.GetOutputText()?.Length ?? 0)]}...");
// Common pattern: keep only messages, discard reasoning
foreach (var item in response.Value.OutputItems)
if (item is MessageResponseItem)
conversation.Add(item); // orphan message → 400 on next turn
}
catch (Exception e)
{
Console.WriteLine($" ERROR: {e.Message[..Math.Min(120, e.Message.Length)]}");
break;
}
}
// Turn 2 → 400: Item 'msg_...' was provided without its required preceding item
Reproduced on o4-mini and gpt-5.3-codex. Full cross-language repro (JS, Python, .NET, curl): https://gist.github.com/achandmsft/57886350885cec3af8ef3f456ed529cf
Environment
- OpenAI .NET SDK v2.9.1
- .NET 9
- Windows 11, also reproduced on Linux
Reasoning+message items must appear as consecutive pairs in
input, but nothing documents this. The most common pattern — filteringresponse.outputto keep only messages — silently produces orphaned items → 400 on the next turn.This broke OpenClaw (64.9k forks) on
gpt-5.3-codex. They had to adddowngradeOpenAIReasoningBlocks()to strip orphan reasoning items.Tested in .NET, JS, Python, Go, Java — identical results across all 5 SDKs. This is an API-level constraint, not SDK-specific. Confirmed via curl (see gist). The SDK types don't prevent building
inputarrays that violate it.The .NET SDK uses shared
ResponseItemtypes for both input and output — output items pass back as input without compile errors, which makes the broken pattern especially easy to write.* Nondeterministic — o4-mini sometimes returns reasoning-only output (no message to orphan). Codex with reasoning=high reliably returns both items.
Workaround:
PreviousResponseId. For manual history, always pass reasoning+message pairs together.Related:
To Reproduce
Reproduced on o4-mini and gpt-5.3-codex. Full cross-language repro (JS, Python, .NET, curl): https://gist.github.com/achandmsft/57886350885cec3af8ef3f456ed529cf
Environment