Skip to content

Comments

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

Open
hztBUAA wants to merge 3 commits intoChainlit:mainfrom
hztBUAA:fix/base64-file-reload-crash
Open

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

Conversation

@hztBUAA
Copy link
Contributor

@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. Preserves bytes, fixes None handling, resolves element URLs on resume, and adds clear error reporting across the resume flow.

  • Bug Fixes
    • Preserve raw bytes in Element.from_dict(); fix None handling for url/objectKey/mime.
    • Frontend resume: filter null elements and resolve URLs from chainlitKey.
    • Gracefully handle storage read-URL failures in ChainlitDataLayer/DynamoDB; log and continue.
    • Harden resume flow: catch errors in socket (including emit and step restore) and GET /thread; send clear errors to the client.
    • Add tests for None handling, convert_element_row_to_dict, get_element(), and Dynamo read-URL failure.

Written for commit ec951a0. 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.

@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?

Copy link
Collaborator

@dokterbob dokterbob left a comment

Choose a reason for hiding this comment

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

See comments.

@hztBUAA
Copy link
Contributor Author

hztBUAA commented Feb 25, 2026

Addressed the review comments in commit f79867bc.

Updates:

  • Fixed connection_successful error path to avoid duplicate resume error emissions when resume_thread raises.
  • Moved test imports (AsyncMock) to module top-level.
  • Added DynamoDB coverage (TestDynamoThreadElementUrlFailure) to validate behavior when storage URL resolution fails, so both modified data layers are now covered.

I could not run tests in this environment because pytest is not installed locally, but the changes are scoped to the review points above.

@hztBUAA
Copy link
Contributor Author

hztBUAA commented Feb 25, 2026

Follow-up after running full local verification in a project-specific environment.

Updated branch head: ec951a0a

Validation run (from backend/):

  • .venv/bin/python -m ruff format --check chainlit/socket.py tests/data/test_chainlit_data_layer.py
  • .venv/bin/python -m ruff check chainlit/socket.py tests/data/test_chainlit_data_layer.py
  • PYTHONPATH=. .venv/bin/python -m pytest tests/data/test_chainlit_data_layer.py -q

Results:

  • Ruff format: pass
  • Ruff lint: pass
  • Pytest: 10 passed (with one existing pydantic deprecation warning)

This PR is now validated end-to-end locally with no pending formatter/lint/test issues on the touched scope.

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

3 participants