Thank you for your time on the matter, I was hoping it was something obvious, but I’m still scratching my head. Here’s a bash script to replicate my issue:
#!/usr/bin/env bash
set -eu
: "${OPENAI_API_KEY:?Set OPENAI_API_KEY first}"
BASE="https://api.openai.com/v1"
MODEL="${MODEL:-gpt-5.4-mini}"
SLEEP_AFTER_ASSISTANT_ADD="${SLEEP_AFTER_ASSISTANT_ADD:-2}"
AUTH=(
-H "Authorization: Bearer $OPENAI_API_KEY"
-H "Content-Type: application/json"
)
SYSTEM_TEXT="System message containing instructions."
USER_TEXT="Initial user message"
ASSISTANT_TEXT="Assistant message that will disappear."
RESPONSES_INPUT_TEXT="Has the user provided answers to the outstanding fields? "
list_items() {
curl -sS --globoff \
"$BASE/conversations/$CONV_ID/items?order=asc&include[]=message.input_image.image_url&limit=100" \
"${AUTH[@]}"
}
retrieve_item() {
local item_id="$1"
curl -sS --globoff \
"$BASE/conversations/$CONV_ID/items/$item_id?include[]=message.input_image.image_url" \
"${AUTH[@]}"
}
echo
echo "1) Create conversation with initial system message"
CREATE_CONV_JSON=$(
jq -n --arg text "$SYSTEM_TEXT" '{
items: [
{
role: "system",
content: $text,
type: "message"
}
]
}'
)
CREATE_CONV_RESPONSE=$(
curl -sS "$BASE/conversations" \
"${AUTH[@]}" \
-d "$CREATE_CONV_JSON"
)
echo "$CREATE_CONV_RESPONSE" | jq .
CONV_ID=$(echo "$CREATE_CONV_RESPONSE" | jq -r '.id')
if [[ "$CONV_ID" == "null" || -z "$CONV_ID" ]]; then
echo "ERROR: Failed to create conversation"
exit 1
fi
echo "CONV_ID=$CONV_ID"
echo
echo "2) Add user message to conversation"
ADD_USER_JSON=$(
jq -n --arg text "$USER_TEXT" '{
items: [
{
type: "message",
role: "user",
content: [
{
type: "input_text",
text: $text
}
]
}
]
}'
)
ADD_USER_RESPONSE=$(
curl -sS "$BASE/conversations/$CONV_ID/items" \
"${AUTH[@]}" \
-d "$ADD_USER_JSON"
)
echo "$ADD_USER_RESPONSE" | jq .
USER_ITEM_ID=$(echo "$ADD_USER_RESPONSE" | jq -r '.data[0].id')
echo "USER_ITEM_ID=$USER_ITEM_ID"
echo
echo "3) Add hard-coded assistant EasyInputMessage to conversation"
ADD_ASSISTANT_JSON=$(
jq -n --arg text "$ASSISTANT_TEXT" '{
items: [
{
type: "message",
role: "assistant",
content: $text,
phase: "final_answer"
}
]
}'
)
ADD_ASSISTANT_RESPONSE=$(
curl -sS "$BASE/conversations/$CONV_ID/items" \
"${AUTH[@]}" \
-d "$ADD_ASSISTANT_JSON"
)
echo "$ADD_ASSISTANT_RESPONSE" | jq .
ASSISTANT_ITEM_ID=$(echo "$ADD_ASSISTANT_RESPONSE" | jq -r '.data[0].id')
if [[ "$ASSISTANT_ITEM_ID" == "null" || -z "$ASSISTANT_ITEM_ID" ]]; then
echo "ERROR: Failed to create assistant item"
exit 1
fi
echo "ASSISTANT_ITEM_ID=$ASSISTANT_ITEM_ID"
echo
echo "4) Retrieve hard-coded assistant item before Responses call"
RETRIEVE_BEFORE=$(retrieve_item "$ASSISTANT_ITEM_ID")
echo "$RETRIEVE_BEFORE" | jq .
echo
echo "5) List all items before Responses call"
LIST_BEFORE=$(list_items)
echo "$LIST_BEFORE" | jq .
echo
echo "5a) Check hard-coded assistant item is present before Responses call"
if echo "$LIST_BEFORE" | jq -e --arg id "$ASSISTANT_ITEM_ID" '.data[] | select(.id == $id)' >/dev/null; then
echo "OK: Assistant item is present in list before Responses call"
else
echo "ERROR: Assistant item is already missing before Responses call"
exit 1
fi
echo
echo "6) Wait briefly to rule out immediate write/read timing issues"
echo "Sleeping ${SLEEP_AFTER_ASSISTANT_ADD}s..."
sleep "$SLEEP_AFTER_ASSISTANT_ADD"
echo
echo "7) Create model response using SAME conversation ID"
CREATE_RESPONSE_JSON=$(
jq -n \
--arg model "$MODEL" \
--arg conv "$CONV_ID" \
--arg text "$RESPONSES_INPUT_TEXT" \
'{
model: $model,
conversation: $conv,
store: true,
input: [
{
content: $text,
role: "system",
type: "message"
}
]
}'
)
CREATE_RESPONSE_RESPONSE=$(
curl -sS "$BASE/responses" \
"${AUTH[@]}" \
-d "$CREATE_RESPONSE_JSON"
)
echo "$CREATE_RESPONSE_RESPONSE" | jq .
RESPONSE_ID=$(echo "$CREATE_RESPONSE_RESPONSE" | jq -r '.id')
echo "RESPONSE_ID=$RESPONSE_ID"
echo
echo "8) Retrieve original hard-coded assistant item after Responses call"
RETRIEVE_AFTER=$(retrieve_item "$ASSISTANT_ITEM_ID")
echo "$RETRIEVE_AFTER" | jq .
echo
echo "9) List all items after Responses call"
LIST_AFTER=$(list_items)
echo "$LIST_AFTER" | jq .
echo
echo "10) Repro checks"
echo
echo "Check A: Is original hard-coded assistant item still present in the list?"
if echo "$LIST_AFTER" | jq -e --arg id "$ASSISTANT_ITEM_ID" '.data[] | select(.id == $id)' >/dev/null; then
echo "NOT REPRODUCED: Original assistant item is still present in the conversation list"
else
echo "REPRODUCED: Original assistant item is missing from the conversation list after Responses call"
fi
echo
echo "Check B: Does direct retrieval now return role=unknown and <unknown message>?"
if echo "$RETRIEVE_AFTER" | jq -e '
.role == "unknown"
and (.content | length > 0)
and (.content[0].text == "<unknown message>")
' >/dev/null; then
echo "REPRODUCED: Direct retrieval returns role=unknown and <unknown message>"
else
echo "NOT REPRODUCED: Direct retrieval did not return the unknown-message placeholder"
fi
echo
echo "Check C: Show before/after summary for the original assistant item"
echo "Before:"
echo "$RETRIEVE_BEFORE" | jq '{
id,
role,
type,
status,
content
}'
echo "After:"
echo "$RETRIEVE_AFTER" | jq '{
id,
role,
type,
status,
content
}'
echo
echo "Done."
echo "CONV_ID=$CONV_ID"
echo "ASSISTANT_ITEM_ID=$ASSISTANT_ITEM_ID"
echo "MODEL=$MODEL"
Here’s a play by play of what I’m doing. Hopefully this will make apparent what amateur hour mistake I’ve made. Or we’ve stumbled upon a little bug that I’m going to have to navigate around until our AI overlords fix it.
I create a conversation:
curl --location 'https://api.openai.com/v1/conversations' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer TOKEN' \
--data '{
"items": [
{
"role": "system",
"content": "System message containing instructions.",
"type": "message"
}
]
}'
I get the expected response:
{
"id": "conv_69f5871bb0948193bd42bd2a8649e89f08829a4c83cbc622",
"object": "conversation",
"created_at": 1777698587,
"metadata": {}
}
I add a user message to the conversation:
curl --location 'https://api.openai.com/v1/conversations/conv_69f5871bb0948193bd42bd2a8649e89f08829a4c83cbc622/items' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer TOKEN' \
--data '{
"items": [
{
"type": "message",
"role": "user",
"content": [
{
"type": "input_text",
"text": "Initial user message"
}
]
}
]
}'
I get the expected response:
{
"object": "list",
"data": [
{
"id": "msg_69f5878674a481939362d4e7bed94b3808829a4c83cbc622",
"type": "message",
"status": "completed",
"content": [
{
"type": "input_text",
"text": "Initial user message"
}
],
"role": "user"
}
],
"first_id": "msg_69f5878674a481939362d4e7bed94b3808829a4c83cbc622",
"has_more": false,
"last_id": "msg_69f5878674a481939362d4e7bed94b3808829a4c83cbc622"
}
I then add a hard coded assistant message. This message will eventually become corrupted and disappear from the list:
curl --location 'https://api.openai.com/v1/conversations/conv_69f5871bb0948193bd42bd2a8649e89f08829a4c83cbc622/items' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer TOKEN' \
--data '{
"items": [
{
"type": "message",
"role": "assistant",
"content": "Assistant message that will disappear.",
"phase": "final_answer"
}
]
}'
I get the expected response:
{
"object": "list",
"data": [
{
"id": "msg_69f587a159c881939bd111a2c71142c908829a4c83cbc622",
"type": "message",
"status": "completed",
"content": [
{
"type": "input_text",
"text": "Assistant message that will disappear."
}
],
"role": "assistant"
}
],
"first_id": "msg_69f587a159c881939bd111a2c71142c908829a4c83cbc622",
"has_more": false,
"last_id": "msg_69f587a159c881939bd111a2c71142c908829a4c83cbc622"
}
To double check that the message was added to the conversation, I make a call to get all items in the conversation so far:
curl --location --globoff 'https://api.openai.com/v1/conversations/conv_69f5871bb0948193bd42bd2a8649e89f08829a4c83cbc622/items?order=asc&include[]=message.input_image.image_url&limit=100' \
--header 'Authorization: Bearer TOKEN'
The hard coded assistant message is there:
{
"object": "list",
"data": [
{
"id": "msg_69f5871bb0bc8193be30f0b1e80313a208829a4c83cbc622",
"type": "message",
"status": "completed",
"content": [
{
"type": "input_text",
"text": "System message containing instructions."
}
],
"role": "system"
},
{
"id": "msg_69f5878674a481939362d4e7bed94b3808829a4c83cbc622",
"type": "message",
"status": "completed",
"content": [
{
"type": "input_text",
"text": "Initial user message"
}
],
"role": "user"
},
{
"id": "msg_69f587a159c881939bd111a2c71142c908829a4c83cbc622",
"type": "message",
"status": "completed",
"content": [
{
"type": "input_text",
"text": "Assistant message that will disappear."
}
],
"role": "assistant"
}
],
"first_id": "msg_69f5871bb0bc8193be30f0b1e80313a208829a4c83cbc622",
"has_more": false,
"last_id": "msg_69f587a159c881939bd111a2c71142c908829a4c83cbc622"
}
I then make a call to the responses endpoint:
curl --location 'https://api.openai.com/v1/responses' \
--header 'Content-Type: application/json' \
--header 'Authorization: Bearer TOKEN' \
--data '{
"model": "gpt-5.4-mini",
"conversation": "conv_69f5871bb0948193bd42bd2a8649e89f08829a4c83cbc622",
"input": [
{
"content": "Has the user provided answers to the outstanding fields? ",
"role": "system",
"type": "message"
}
]
}'
And get a response from it:
{
"id": "resp_08829a4c83cbc6220069f587f985c88193a5a04f15b4d5406e",
"object": "response",
"created_at": 1777698810,
"status": "completed",
"background": false,
"billing": {
"payer": "developer"
},
"completed_at": 1777698811,
"conversation": {
"id": "conv_69f5871bb0948193bd42bd2a8649e89f08829a4c83cbc622"
},
"error": null,
"frequency_penalty": 0.0,
"incomplete_details": null,
"instructions": null,
"max_output_tokens": null,
"max_tool_calls": null,
"model": "gpt-5.4-mini-2026-03-17",
"moderation": null,
"output": [
{
"id": "msg_08829a4c83cbc6220069f587fafab48193bcb5c61162b541a5",
"type": "message",
"status": "completed",
"content": [
{
"type": "output_text",
"annotations": [],
"logprobs": [],
"text": "No."
}
],
"phase": "final_answer",
"role": "assistant"
}
],
"parallel_tool_calls": true,
"presence_penalty": 0.0,
"previous_response_id": null,
"prompt_cache_key": null,
"prompt_cache_retention": "in_memory",
"reasoning": {
"effort": "none",
"summary": null
},
"safety_identifier": null,
"service_tier": "default",
"store": true,
"temperature": 1.0,
"text": {
"format": {
"type": "text"
},
"verbosity": "medium"
},
"tool_choice": "auto",
"tools": [],
"top_logprobs": 0,
"top_p": 0.98,
"truncation": "disabled",
"usage": {
"input_tokens": 45,
"input_tokens_details": {
"cached_tokens": 0
},
"output_tokens": 6,
"output_tokens_details": {
"reasoning_tokens": 0
},
"total_tokens": 51
},
"user": null,
"metadata": {}
}
I then make another call to get all items in the conversation:
curl --location --globoff 'https://api.openai.com/v1/conversations/conv_69f5871bb0948193bd42bd2a8649e89f08829a4c83cbc622/items?order=asc&include[]=message.input_image.image_url&limit=100' \
--header 'Authorization: Bearer TOKEN'
But now that hard coded assistant message has disappeared:
{
"object": "list",
"data": [
{
"id": "msg_69f5871bb0bc8193be30f0b1e80313a208829a4c83cbc622",
"type": "message",
"status": "completed",
"content": [
{
"type": "input_text",
"text": "System message containing instructions."
}
],
"role": "system"
},
{
"id": "msg_69f5878674a481939362d4e7bed94b3808829a4c83cbc622",
"type": "message",
"status": "completed",
"content": [
{
"type": "input_text",
"text": "Initial user message"
}
],
"role": "user"
},
{
"id": "msg_08829a4c83cbc6220069f587fa76f48193aee927a5bcde3104",
"type": "message",
"status": "completed",
"content": [
{
"type": "input_text",
"text": "Has the user provided answers to the outstanding fields? "
}
],
"role": "system"
},
{
"id": "msg_08829a4c83cbc6220069f587fafab48193bcb5c61162b541a5",
"type": "message",
"status": "completed",
"content": [
{
"type": "output_text",
"annotations": [],
"logprobs": [],
"text": "No."
}
],
"role": "assistant"
}
],
"first_id": "msg_69f5871bb0bc8193be30f0b1e80313a208829a4c83cbc622",
"has_more": false,
"last_id": "msg_08829a4c83cbc6220069f587fafab48193bcb5c61162b541a5"
}
If I ask for it specifically:
curl --location --globoff 'https://api.openai.com/v1/conversations/conv_69f5871bb0948193bd42bd2a8649e89f08829a4c83cbc622/items/msg_69f587a159c881939bd111a2c71142c908829a4c83cbc622?order=asc&include[]=message.input_image.image_url&limit=100' \
--header 'Authorization: Bearer TOKEN'
It returns the <unknown message> tag:
{
"id": "msg_69f587a159c881939bd111a2c71142c908829a4c83cbc622",
"type": "message",
"status": "completed",
"content": [
{
"type": "text",
"text": "<unknown message>"
}
],
"role": "unknown"
}