Skip to content

openai-codex provider sends empty input[] when context has only systemPrompt → ChatGPT 400 #73820

@Gusty3055

Description

@Gusty3055

Summary

When the openai-codex provider (api openai-codex-responses, baseUrl
https://chatgpt.com/backend-api/codex) is invoked with a context whose
messages array is empty but whose systemPrompt is set, the resulting
request body contains input: []. ChatGPT's codex backend rejects this with
a 400:

One of "input" or "previous_response_id" or 'prompt' or 'conversation_id'
must be provided.

In practice the 400 closes the upstream socket before the streaming consumer
classifies the failure, so most calls surface to the user as a 15–31s
timeout rather than the underlying error — making this hard to diagnose
from logs alone. We caught the rawError on one occurrence:

[agent/embedded] embedded run agent end: ... isError=true model=gpt-5.5
  provider=openai-codex
  error=One of "input" or "previous_response_id" or 'prompt' or 'conversation_id' must be provided.
  rawError=One of "input" or "previous_response_id" or 'prompt' or 'conversation_id' must be provided.

with 27 sibling entries from the same workload showing
decision=surface_error reason=timeout from=openai-codex/gpt-5.5 for what
appears to be the same root cause.

Environment

  • openclaw 2026.4.26
  • macOS 15 (Darwin 25.4.0)
  • Auth: ChatGPT OAuth (access_token still valid; not an auth failure)
  • Triggering plugin: active-memory (memory-flush bootstrap run)
  • Affected models: openai-codex/gpt-5.4, openai-codex/gpt-5.5

Root cause (suspected)

buildOpenAIResponsesParams in provider-stream-*.js:

```js
const messages = convertResponsesMessages(model, context, ..., {
includeSystemPrompt: !isCodexResponses, // codex skips system msg
supportsDeveloperRole
});
const params = {
model: model.id,
input: messages, // empty when caller has no user turn
stream: true,
...isCodexResponses ? { instructions: buildOpenAICodexResponsesInstructions(context) } : {},
...
};
```

For codex, the system prompt is moved out of messages into instructions
(correct per ChatGPT's codex contract). But if the caller passes
{ systemPrompt: "...", messages: [] } — which active-memory's flush
bootstrap does — params.input ends up as []. The codex backend
treats that as missing and 400s.

Proposed fix

Inside buildOpenAIResponsesParams, after the convertResponsesMessages
call, ensure codex requests carry at least one input item when the only
content available is the system prompt:

```js
if (isCodexResponses && messages.length === 0 && context.systemPrompt) {
messages.push({
role: "user",
content: [{
type: "input_text",
text: sanitizeTransportPayloadText(
stripSystemPromptCacheBoundary(context.systemPrompt)
)
}]
});
}
```

Alternatives considered:

  • Set includeSystemPrompt: true for codex unconditionally — wrong, the
    ChatGPT codex backend expects system-as-instructions, not as a message.
  • Refuse to dispatch when messages.length === 0 — surfaces a clearer
    error but doesn't restore functionality for the (legitimate) bootstrap case.

Repro

  1. Configure an openai-codex provider with gpt-5.5 (or any
    openai-codex-responses model).
  2. Enable active-memory plugin, set
    plugins.entries.active-memory.config.activeProvider = openai-codex,
    activeModel = gpt-5.5.
  3. Trigger an active-memory flush (e.g. send a Slack message that
    crosses the flush threshold).
  4. Observe `[agent/embedded] embedded run failover decision: ... reason=timeout from=openai-codex/gpt-5.5` in `gateway.err.log`.
  5. Occasionally a single entry surfaces the underlying 400 rawError
    above.

Workaround

Point active-memory's activeProvider/activeModel at any non-codex
provider (e.g. an lmstudio-served model). Confirms codex is the
isolated failure point.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions