-
Notifications
You must be signed in to change notification settings - Fork 1.3k
Description
This bug was identified by Claude.
Bug
When a user calls POST /v1/responses with store: false and a conversation ID, the response object is correctly not persisted, but conversation messages and items are still written to the database.
This violates the semantics of store=false, which should prevent all server-side persistence for the request.
Root cause
In _create_streaming_response() (openai_responses.py, lines 1118-1130):
# Incremental persistence (correctly gated)
if store:
await self._persist_streaming_state(...)
# Conversation sync (BUG: does not check `store`)
if (
stream_chunk.type in {"response.completed", "response.incomplete"}
and final_response
and failed_response is None
):
if conversation:
# These two writes execute even when store=False
await self._sync_response_to_conversation(conversation, input, output_items)
await self.responses_store.store_conversation_messages(conversation, messages_to_store)The store flag correctly gates _persist_streaming_state (line 1110), but the conversation sync block (line 1125) only checks if conversation: and ignores store entirely.
Reproduction
Step 1: Create a conversation
$ curl -s http://localhost:8321/v1/conversations \
-H "Content-Type: application/json" -d '{}'{
"id": "conv_cfe5bb383e510ebcd729360c012bd07c3c8e5643c38789da",
"object": "conversation",
"created_at": 1774477837,
"metadata": null,
"items": null
}Step 2: Create a response with store=false on this conversation
$ curl -s http://localhost:8321/v1/responses \
-H "Content-Type: application/json" \
-d '{"model":"openai/gpt-4o-mini","input":"What is 2+2?","store":false,"conversation":"conv_cfe5bb383e510ebcd729360c012bd07c3c8e5643c38789da"}'{
"id": "resp_0217f2ac-425e-4d8a-8779-aceb7c6700b3",
"object": "response",
"status": "completed",
"output": [
{
"content": [{"text": "2 + 2 equals 4.", "type": "output_text"}],
"role": "assistant",
"type": "message",
"id": "msg_14412d01-45fe-445a-9603-e254b293c372",
"status": "completed"
}
],
"store": false
}Step 3: Verify the response object was NOT stored (correct)
$ curl -s http://localhost:8321/v1/responses/resp_0217f2ac-425e-4d8a-8779-aceb7c6700b3{
"detail": "Response 'resp_0217f2ac-425e-4d8a-8779-aceb7c6700b3' not found. Use 'client.responses.list()' to list available Responses."
}The response object was correctly not persisted — store=false was honored here.
Step 4: Check conversation items (BUG)
$ curl -s http://localhost:8321/v1/conversations/conv_cfe5bb383e510ebcd729360c012bd07c3c8e5643c38789da/items{
"object": "list",
"data": [
{
"content": [{"text": "2 + 2 equals 4.", "type": "output_text"}],
"role": "assistant",
"type": "message",
"id": "msg_14412d01-45fe-445a-9603-e254b293c372",
"status": "completed"
},
{
"content": [{"text": "What is 2+2?", "type": "input_text"}],
"role": "user",
"type": "message",
"id": "msg_5863422ba1218f2c9fa62c6ed47114c9c8522072037854a6",
"status": null
}
],
"first_id": "msg_14412d01-45fe-445a-9603-e254b293c372",
"last_id": "msg_5863422ba1218f2c9fa62c6ed47114c9c8522072037854a6",
"has_more": false
}Both the user input and assistant output were persisted to the conversation despite store=false.
Impact
- Privacy: A user explicitly opting out of storage (
store=false) still has their conversation content written to the database. This is especially problematic for sensitive or PII-containing queries. - Semantic violation: The
storeparameter's contract is broken — partial persistence is worse than no persistence because the user believes their data was not stored. - Storage leak: Ephemeral conversations accumulate in the
conversation_messagestable with no cleanup path since the user never intended them to be stored.
Fix
Add and store to the condition on line 1120:
if (
stream_chunk.type in {"response.completed", "response.incomplete"}
and final_response
and failed_response is None
and store # <-- gate conversation sync on store flag
):