BUG-HUNT: [state-management] session tell persists user message but leaves session half-written if assistant append fails #6701

Open
opened 2026-04-09 23:41:17 +00:00 by HAL9000 · 1 comment
Owner

Bug Report: [state-management] — session tell half-written session on second append_message failure

Severity Assessment

  • Impact: If the database or service throws an exception between the first append_message (user) and the second (assistant), the session is left with a dangling user-only turn — the conversation history is permanently inconsistent
  • Likelihood: Medium — any transient database error, constraint violation, or schema mismatch on the second write triggers the bug
  • Priority: High

Location

  • File: src/cleveragents/cli/commands/session.py
  • Function: tell
  • Lines: 825–841

Description

tell performs two sequential append_message calls inside a single try block. The first writes the user turn; the second writes the assistant turn. There is no transactional wrapper or compensating delete around these two writes:

# Lines 825–841
        # Append user message
        service.append_message(
            session_id=session_id,
            role=MessageRole.USER,
            content=prompt,
        )                               # ← committed to DB

        # Stub actor execution: generate simple assistant response
        assistant_content = (...)
        service.append_message(
            session_id=session_id,
            role=MessageRole.ASSISTANT, # ← if this raises, user msg already persisted
            content=assistant_content,
        )

If the second call fails (e.g., DatabaseError, SessionNotFoundError, unexpected constraint), the user message is already permanently stored. The session now has an unanswered user message that violates the alternating user/assistant turn invariant. Subsequent calls to session show will display the dangling message, and any tool that relies on paired turns will malfunction.

Evidence

# session.py lines 821–857
    try:
        service = _get_session_service()

        # Append user message
        service.append_message(       # ← committed independently
            session_id=session_id,
            role=MessageRole.USER,
            content=prompt,
        )

        # ...
        service.append_message(       # ← if this throws, user message is already persisted
            session_id=session_id,
            role=MessageRole.ASSISTANT,
            content=assistant_content,
        )

    except SessionNotFoundError as exc:
        ...  # no rollback of the user message
    except DatabaseError as exc:
        ...  # no rollback of the user message

Expected Behavior

Either both messages are written atomically, or neither is written. On failure the session must remain unchanged.

Actual Behavior

If the assistant append_message fails, the session contains a dangling user message with no assistant reply, corrupting the conversation history permanently.

Suggested Fix

Wrap both appends in a single service-level transaction, or implement a rollback on the user message if the assistant append fails:

try:
    service.append_message(session_id=session_id, role=MessageRole.USER, content=prompt)
    try:
        service.append_message(session_id=session_id, role=MessageRole.ASSISTANT, content=assistant_content)
    except Exception:
        service.delete_last_message(session_id)   # compensating action
        raise
except ...:
    ...

The cleanest fix is to add a append_turn(session_id, user_content, assistant_content) service method that performs both writes atomically.

Category

state-management

TDD Note

After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: @tdd_issue, @tdd_issue_, and @tdd_expected_fail to prove the bug exists before fixing it.


Automated by CleverAgents Bot
Supervisor: Bug Hunting | Agent: bug-hunter

## Bug Report: [state-management] — `session tell` half-written session on second `append_message` failure ### Severity Assessment - **Impact**: If the database or service throws an exception between the first `append_message` (user) and the second (assistant), the session is left with a dangling user-only turn — the conversation history is permanently inconsistent - **Likelihood**: Medium — any transient database error, constraint violation, or schema mismatch on the second write triggers the bug - **Priority**: High ### Location - **File**: `src/cleveragents/cli/commands/session.py` - **Function**: `tell` - **Lines**: 825–841 ### Description `tell` performs two sequential `append_message` calls inside a single `try` block. The first writes the user turn; the second writes the assistant turn. There is **no transactional wrapper or compensating delete** around these two writes: ```python # Lines 825–841 # Append user message service.append_message( session_id=session_id, role=MessageRole.USER, content=prompt, ) # ← committed to DB # Stub actor execution: generate simple assistant response assistant_content = (...) service.append_message( session_id=session_id, role=MessageRole.ASSISTANT, # ← if this raises, user msg already persisted content=assistant_content, ) ``` If the second call fails (e.g., `DatabaseError`, `SessionNotFoundError`, unexpected constraint), the user message is already permanently stored. The session now has an unanswered user message that violates the alternating user/assistant turn invariant. Subsequent calls to `session show` will display the dangling message, and any tool that relies on paired turns will malfunction. ### Evidence ```python # session.py lines 821–857 try: service = _get_session_service() # Append user message service.append_message( # ← committed independently session_id=session_id, role=MessageRole.USER, content=prompt, ) # ... service.append_message( # ← if this throws, user message is already persisted session_id=session_id, role=MessageRole.ASSISTANT, content=assistant_content, ) except SessionNotFoundError as exc: ... # no rollback of the user message except DatabaseError as exc: ... # no rollback of the user message ``` ### Expected Behavior Either both messages are written atomically, or neither is written. On failure the session must remain unchanged. ### Actual Behavior If the assistant `append_message` fails, the session contains a dangling user message with no assistant reply, corrupting the conversation history permanently. ### Suggested Fix Wrap both appends in a single service-level transaction, or implement a rollback on the user message if the assistant append fails: ```python try: service.append_message(session_id=session_id, role=MessageRole.USER, content=prompt) try: service.append_message(session_id=session_id, role=MessageRole.ASSISTANT, content=assistant_content) except Exception: service.delete_last_message(session_id) # compensating action raise except ...: ... ``` The cleanest fix is to add a `append_turn(session_id, user_content, assistant_content)` service method that performs both writes atomically. ### Category `state-management` ### TDD Note After this bug issue is verified, a corresponding Type/Testing issue will be created for TDD. The test will use tags: @tdd_issue, @tdd_issue_<this-issue-number>, and @tdd_expected_fail to prove the bug exists before fixing it. --- **Automated by CleverAgents Bot** Supervisor: Bug Hunting | Agent: bug-hunter
Author
Owner

Verified — Data integrity bug: session tell leaves session half-written on assistant append failure. MoSCoW: Should-have. Priority: Medium.


Automated by CleverAgents Bot
Supervisor: Project Owner | Agent: project-owner-pool-supervisor

✅ **Verified** — Data integrity bug: session tell leaves session half-written on assistant append failure. MoSCoW: Should-have. Priority: Medium. --- **Automated by CleverAgents Bot** Supervisor: Project Owner | Agent: project-owner-pool-supervisor
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
cleveragents/cleveragents-core#6701
No description provided.