Skip to content

Comments

fix: prevent crash when reloading conversations with Base64-encoded files#2799

Open
hztBUAA wants to merge 1 commit intoChainlit:mainfrom
hztBUAA:fix/base64-file-reload-crash
Open

fix: prevent crash when reloading conversations with Base64-encoded files#2799
hztBUAA wants to merge 1 commit intoChainlit:mainfrom
hztBUAA:fix/base64-file-reload-crash

Conversation

@hztBUAA
Copy link

@hztBUAA hztBUAA commented Feb 20, 2026

Summary

Fixes #2784 — Conversations with Base64-encoded file attachments crash on reload.

Root causes identified and fixed:

  • Element.from_dict() called str() on bytes content, producing "b'\x89PNG...'" instead of preserving raw bytes
  • ChainlitDataLayer.get_element() converted None url/objectKey/mime to the string "None" via str(None)
  • Frontend resume_thread handler did not resolve element URLs from chainlitKey, unlike the element and set_sidebar_elements handlers
  • Storage client failures during get_thread crashed the entire resume flow — added try/except in both ChainlitDataLayer and DynamoDB data layers
  • Socket connection_successful handler had no error handling around thread resume — added try/except with error message to the frontend
  • HTTP get_thread endpoint had no error handling — added try/except with 500 response

Changes

File Change
backend/chainlit/element.py Preserve bytes content in from_dict() instead of str() conversion
backend/chainlit/data/chainlit_data_layer.py Fix get_element() None-to-string bug; add storage error handling in get_thread()
backend/chainlit/data/dynamodb.py Add storage error handling in get_thread()
backend/chainlit/socket.py Add error handling around thread resume
backend/chainlit/server.py Add error handling for HTTP get_thread endpoint
libs/react-client/src/useChatSession.ts Resolve element URLs from chainlitKey in resume_thread handler
backend/tests/data/test_chainlit_data_layer.py Add tests for element None handling and get_element

Test plan

  • _convert_element_row_to_dict preserves None url/objectKey values
  • get_element returns None (not string "None") for missing url/objectKey/mime
  • Frontend resolves element URLs from chainlitKey on thread resume
  • Storage client failures don't crash thread resume
  • Manual test: create conversation with Base64 file attachment, reload page

🤖 Generated with Claude Code


Summary by cubic

Prevents crashes when reloading conversations with Base64-encoded file attachments. Fixes bytes/None handling, resolves missing element URLs on resume, and adds error handling across the resume/get_thread path.

  • Bug Fixes
    • Preserve raw bytes in Element.from_dict() (no str() on bytes).
    • Keep None for url/objectKey/mime in get_element() (no "None" strings).
    • Client resolves element URLs from chainlitKey during resume.
    • Catch storage read-URL failures in ChainlitDataLayer and DynamoDB; continue safely.
    • Wrap thread resume in socket and GET /thread in server with try/except; surface clear errors.
    • Add tests for element None handling and get_element().

Written for commit c2c5722. Summary will update on new commits.

…iles (Chainlit#2784)

Multiple issues caused conversations with file attachments to crash on reload:

1. Element.from_dict() called str() on bytes content, producing repr like
   "b'\x89PNG...'" instead of preserving the raw bytes.

2. ChainlitDataLayer.get_element() converted None url/objectKey/mime to
   the string "None" via str(None).

3. The frontend resume_thread handler did not resolve element URLs from
   chainlitKey, unlike the element and set_sidebar_elements handlers.

4. Storage client failures during get_thread crashed the entire resume flow.
   Added try/except in ChainlitDataLayer and DynamoDB data layers.

5. The socket connection_successful handler had no error handling around
   thread resume. Added try/except with error messages to the frontend.

6. The HTTP get_thread endpoint had no error handling. Added try/except.
@dosubot dosubot bot added size:L This PR changes 100-499 lines, ignoring generated files. bug Something isn't working labels Feb 20, 2026
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 7 files

Prompt for AI agents (all issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="backend/chainlit/socket.py">

<violation number="1" location="backend/chainlit/socket.py:204">
P2: Exception handling for resume_thread emits an error but then falls through to the existing "Thread not found" branch, resulting in two error emissions for the same failure.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

await context.emitter.send_resume_thread_error(
"Failed to load conversation history."
)
thread = None
Copy link
Contributor

@cubic-dev-ai cubic-dev-ai bot Feb 20, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Exception handling for resume_thread emits an error but then falls through to the existing "Thread not found" branch, resulting in two error emissions for the same failure.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At backend/chainlit/socket.py, line 204:

<comment>Exception handling for resume_thread emits an error but then falls through to the existing "Thread not found" branch, resulting in two error emissions for the same failure.</comment>

<file context>
@@ -194,7 +194,15 @@ async def connection_successful(sid):
+            await context.emitter.send_resume_thread_error(
+                "Failed to load conversation history."
+            )
+            thread = None
+
         if thread:
</file context>
Fix with Cubic

@dokterbob dokterbob added unit-tests Has unit tests. review-me Ready for review! labels Feb 23, 2026
@dokterbob
Copy link
Collaborator

Would love to merge this, please run uv run ruff format over your code! :)


@pytest.mark.asyncio
async def test_get_element_not_found(self):
from unittest.mock import AsyncMock
Copy link
Collaborator

@dokterbob dokterbob Feb 23, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This import might well be at the top! Same for other places.

layer = self._make_layer()
layer.execute_query = AsyncMock(return_value=[])
result = await layer.get_element("thread-1", "nonexistent")
assert result is None
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I notice changes in two data layers but unit tests only for one, what's the reason?

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

Labels

bug Something isn't working review-me Ready for review! size:L This PR changes 100-499 lines, ignoring generated files. unit-tests Has unit tests.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug Report: Chainlit Crashes When Reloading Conversations Containing Base64-Encoded Files Description

2 participants