Skip to content

fix(sessions): handle list content in AdvancedSQLiteSession browsing helpers#3761

Merged
seratch merged 1 commit into
openai:mainfrom
anxkhn:patch-5
Jul 9, 2026
Merged

fix(sessions): handle list content in AdvancedSQLiteSession browsing helpers#3761
seratch merged 1 commit into
openai:mainfrom
anxkhn:patch-5

Conversation

@anxkhn

@anxkhn anxkhn commented Jul 9, 2026

Copy link
Copy Markdown
Contributor

AdvancedSQLiteSession.get_conversation_turns() and find_turns_by_content()
assumed a stored user-message content is always a str. That holds for
plain-text turns, but a valid user message can also carry a list of structured
parts (for example multimodal input_text/input_image items), and the session
stores each item verbatim via json.dumps, so json.loads(...).get("content")
hands these helpers a list.

That leads to two problems in get_conversation_turns(), whose docstring promises
'content': User message content (truncated):

  • When the list has more than 100 parts, content[:100] + "..." evaluates
    list + str and raises TypeError: can only concatenate list (not "str") to list. The surrounding except (json.JSONDecodeError, AttributeError) does not
    catch TypeError, so the exception propagates out of asyncio.to_thread and
    aborts the whole call.
  • When the list has 100 or fewer parts, the len(content) > 100 check is False,
    so the raw list is returned in the content field, which contradicts the
    documented truncated-preview string.

find_turns_by_content() shares the same string-only assumption and returns the
raw list in content too.

The fix adds a small module-level _content_preview helper that coerces non-string
content to a JSON string (json.dumps(content, ensure_ascii=False)) before applying
the existing truncation. get_conversation_turns() uses it with a 100-character
limit and find_turns_by_content() uses it without a limit, so each keeps its prior
behavior for string content exactly while now returning the documented preview
string for list content. full_content still keeps the original value in both
helpers. This mirrors the defensive handling the sibling _validate_turn already
does for its own preview slice.

Test plan

Added two regression tests in
tests/extensions/memory/test_advanced_sqlite_session.py (the existing suite only
exercised plain-string content):

  • test_get_conversation_turns_with_list_content adds a short list-content turn and
    a 101-part list-content turn, then asserts get_conversation_turns() returns
    without raising, that content is a str for both, that the long one is
    truncated with a trailing ..., and that full_content keeps the list.
  • test_find_turns_by_content_with_list_content asserts a list-content match returns
    a str preview in content while full_content keeps the list.

Verified red -> green: reverting only the source change makes
test_get_conversation_turns_with_list_content fail with the TypeError and
test_find_turns_by_content_with_list_content fail because content is a list;
with the fix both pass. Also confirmed the string path is unchanged with an
equivalence check across empty, short, length-100, length-101, longer, and unicode
inputs.

Local checks:

  • uv run pytest tests/extensions/memory/test_advanced_sqlite_session.py -> 45
    passed.
  • uv run pytest tests/extensions/memory/ -> 216 passed, 5 skipped
    (external-service integrations).
  • make format (no changes), make lint clean, pyright on the changed module: 0
    errors.

Issue number

N/A

Checks

  • I've added new tests, if relevant
  • I've run .agents/skills/code-change-verification/scripts/run.sh
  • I've confirmed all verification steps pass
  • If using Codex, I've run /review before submitting this PR

…helpers

get_conversation_turns() and find_turns_by_content() assumed a stored user
message content was always a str. A valid user message can carry a list of
structured parts (multimodal input_text/input_image), which the session stores
verbatim. get_conversation_turns() then either crashed with TypeError when the
list had more than 100 parts (content[:100] + "..." evaluates list + str, which
is not caught by its except clause) or returned the raw list in the 'content'
field for shorter lists, violating the documented truncated-preview contract.
find_turns_by_content() shared the str-only assumption and returned the raw list.

Add a small _content_preview helper that coerces non-string content to a JSON
string before truncating, so 'content' is always the documented preview string
while 'full_content' keeps the original value. Add regression tests covering both
the crash and the contract for list content.
@seratch seratch merged commit 5807751 into openai:main Jul 9, 2026
9 checks passed
@seratch seratch added this to the 0.18.x milestone Jul 9, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants