OpenHands / OpenHands/software-agent-sdk

[Bug]: MessageEvent.visualize returns blank output for an empty TextContent block

Open Beginner friendly
#4,774 2 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

bug llm priority:low sdk
Dominant language
Python
Stars
1.1k
Forks
542
Avg merge
1d 19h
Merged PRs (30d)
137

Description

Is there an existing issue for the same bug?
  • I have searched existing issues and this is not a duplicate.
Bug Description

MessageEvent.visualize handles two semantically empty message representations inconsistently.

A message with no content blocks displays the existing placeholder:

Message(role="user", content=[])
# visualize.plain == "[no text content]"

However, a valid message containing one empty TextContent block produces blank output:

Message(
    role="user",
    content=[TextContent(text="")],
)
# visualize.plain == ""

DefaultConversationVisualizer subsequently skips the second event because its rendered text is empty. The event therefore disappears from conversation visualization instead of displaying [no text content].

Expected Behavior

Both empty representations should produce:

[no text content]

DefaultConversationVisualizer should create a visible event block for both representations. Non-empty text and image content should remain unchanged.

Actual Behavior

The two representations produce different results:

content=[]:
visualize.plain = "[no text content]"
event block created = True

content=[TextContent(text="")]:
visualize.plain = ""
event block created = False

The underlying event remains valid, but the default conversation visualizer omits it.

Steps to Reproduce
  1. Check out release v1.44.1 at commit 9d143aac35c2dcec9cbb046ff9f35ac5eb072f6a.

  2. Set up the development environment:

make build
  1. Create tests/sdk/event/test_message_event_empty_visualization.py:
from openhands.sdk.conversation.visualizer.default import (
    DefaultConversationVisualizer,
)
from openhands.sdk.event.llm_convertible import MessageEvent
from openhands.sdk.llm import Message, TextContent


def make_event(content: list[TextContent]) -> MessageEvent:
    return MessageEvent(
        source="user",
        llm_message=Message(
            role="user",
            content=content,
        ),
    )


def test_empty_text_block_uses_no_text_placeholder():
    no_blocks = make_event([])
    empty_block = make_event([TextContent(text="")])
    visualizer = DefaultConversationVisualizer()

    observed = {
        "no_blocks_text": no_blocks.visualize.plain,
        "empty_block_text": empty_block.visualize.plain,
        "no_blocks_rendered": (
            visualizer._create_event_block(no_blocks) is not None
        ),
        "empty_block_rendered": (
            visualizer._create_event_block(empty_block) is not None
        ),
    }

    assert observed == {
        "no_blocks_text": "[no text content]",
        "empty_block_text": "[no text content]",
        "no_blocks_rendered": True,
        "empty_block_rendered": True,
    }
  1. Run:
uv run pytest \
    tests/sdk/event/test_message_event_empty_visualization.py \
    -q
  1. Observe that the test fails because empty_block_text is "" and empty_block_rendered is False.
Acceptance Criteria
  • MessageEvent.visualize renders [no text content] for content=[].
  • It renders the same placeholder for [TextContent(text="")].
  • DefaultConversationVisualizer does not discard the empty-block event.
  • Non-empty text and image visualization remain unchanged.
  • Regression coverage includes both empty representations.
Installation Method

Source checkout using make build (uv sync --dev)

If you selected "Other", please specify

Not applicable

SDK Version

1.44.1, main@9d143aac35c2dcec9cbb046ff9f35ac5eb072f6a

Version Confirmation
  • I have confirmed this bug exists on the LATEST version of OpenHands SDK
Python Version

3.13.2

Model Name (if applicable)

Not applicable; reproduced by directly invoking deterministic event visualization code.

Operating System

MacOS

Logs and Error Messages
content=[]:
visualize.plain = '[no text content]'
event block created = True

content=[TextContent(text='')]:
visualize.plain = ''
event block created = False

The received assertion value is equivalent to:

{
    "no_blocks_text": "[no text content]",
    "empty_block_text": "",
    "no_blocks_rendered": True,
    "empty_block_rendered": False,
}
Minimal Code Sample
from openhands.sdk.event.llm_convertible import MessageEvent
from openhands.sdk.llm import Message, TextContent


event = MessageEvent(
    source="user",
    llm_message=Message(
        role="user",
        content=[TextContent(text="")],
    ),
)

assert event.visualize.plain == "[no text content]"
Screenshots and Additional Context

No screenshot is required; this is a deterministic unit-level reproduction using public SDK models.

The root cause is that content_to_str returns [""] for the empty block. Although its only string is empty, the list itself is truthy:

text_parts = content_to_str(self.llm_message.content)

if text_parts:
    full_content = "".join(text_parts)
    content.append(full_content)
else:
    content.append("[no text content]")

Joining [""] produces "", so the placeholder branch is skipped. The default visualizer then removes the event:

content = event.visualize

if not content.plain.strip():
    return None

The repository already treats [TextContent(text="")] as blank content in the native test test_message_tool_calls_strip_blank_list_content. This is therefore a supported model shape rather than an invalid handcrafted value.

A possible fix is to decide whether to use the placeholder after joining the text parts:

full_content = "".join(text_parts)

if full_content:
    content.append(full_content)
else:
    content.append("[no text content]")

The relevant implementation is unchanged through main@704cbe6015e3d59cabe04632175d99df2d448999. Targeted issue and pull-request searches found no same-root report.

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start at MessageEvent.visualize and content_to_str, then run tests/sdk/event/test_message_event_empty_visualization.py with the provided uv command. Check the existing native test test_message_tool_calls_strip_blank_list_content for related blank-content behavior; done means both empty representations render [no text content], remain visible, and non-empty text and image output is unchanged.

Written by the indexing model from the issue text.

Assessment

Tech stack
python
Domain
developer-experience
Issue type
Bug
Difficulty
2/5
Estimated time
1-3 hours
Activity status
Active
Clarity
Clearly specified
Newbie friendliness
88/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.