Skip to content

store=false still persists conversation messages to the database #5304

@jaideepr97

Description

@jaideepr97

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 store parameter'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_messages table 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
):

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions