Skip to content

Python: [Bug]: FileHistoryProvider skips unreadable JSON history lines on append #8777

Description

@feiiiiii5

Description

What happened? With the default serialization_format="json", FileHistoryProvider.save_messages() skips history lines it cannot parse instead of reporting them. The append returns successfully, and because the unreadable message is treated as absent from the deduplication set, a replayed transcript writes that message into the file a second time. The same file cannot be read back: get_messages() raises on the very line the append path skipped.

What did I expect? The two formats and the two directions to agree. The MessagePack branch already reads through self._read_msgpack_messages() and propagates the error, and _read_json_messages() propagates it too. Only the JSON branch inlines its own parse loop, which catches Exception and calls logger.debug("failed to parse history line for deduplication") before continue.

Steps to reproduce

  1. save_messages() two messages to a session with the default JSON format.
  2. Leave a line in the .jsonl file that the reader cannot deserialize — a torn final line if the process dies mid-append, a file edited by hand, or a payload shape written by another version.
  3. save_messages() the full transcript again.
  4. The call returns normally and the unreadable message is appended a second time, while get_messages() raises ValueError: Failed to deserialize history line 2 from '.../s.jsonl'.

Code Sample

import asyncio, tempfile
from pathlib import Path

from agent_framework import Message
from agent_framework._sessions import FileHistoryProvider


async def main() -> None:
    provider = FileHistoryProvider(Path(tempfile.mkdtemp()))
    turn = [Message(role="user", contents=["hello"]), Message(role="assistant", contents=["hi there"])]
    await provider.save_messages("s", turn)

    path = provider._session_file_path("s")
    lines = path.read_text(encoding="utf-8").splitlines()
    path.write_text("\n".join([*lines[:-1], "{not json"]) + "\n", encoding="utf-8")

    await provider.get_messages("s")   # ValueError: Failed to deserialize history line 2
    await provider.save_messages("s", turn)   # returns normally; "hi there" is written again


asyncio.run(main())

Error Messages / Stack Traces

ValueError: Failed to deserialize history line 2 from '/tmp/.../s.jsonl'.

Package Versions

agent-framework-core: 1.19.0

Python Version

3.13.3

Additional Context

  • The MessagePack equivalent already raises here, so the two branches of the same abstraction disagree. self._read_msgpack_messages(file_path) on the write path versus an inlined try/except Exception: continue on the JSON path.
  • Reusing self._read_json_messages(file_path) for the deduplication read would make both branches and get_messages() share one parser and one error contract, which is where I would start.
  • ADR 0034 lists "Fail before persistence when an object cannot be restored after a cold start" as a design principle, and describes failures as leaving the original file in place so an application fix or compatible reader can recover it — not as skipping records.
  • I did not find a test covering either branch's behaviour on an unreadable record.

I am going to take this on unless you would rather handle it differently — happy to send a PR with a regression test covering both formats.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

agentsUsage: [Issues, PRs], Target: Single agentpythonUsage: [Issues, PRs], Target: PythonreproducedUsage: [Issues], Target: all issues that can be reproduced by the triage workflow

Type

Projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions