chainlit icon indicating copy to clipboard operation
chainlit copied to clipboard

Command field not persisted to frontend when resuming threads

Open Sanady opened this issue 1 week ago • 0 comments
trafficstars

Describe the bug

The command field is not serialized to the frontend when resuming chat threads, causing command badges to disappear when switching between conversations. The database schema includes a command TEXT column that persists correctly, but SQLAlchemyDataLayer.get_thread() does not include this field in the step serialization sent to the React frontend, even though the frontend has rendering logic for it.

To Reproduce

Steps to reproduce the behavior:

  1. Set up a Chainlit application with PostgreSQL data persistence using SQLAlchemyDataLayer
  2. Configure slash commands using cl.context.emitter.set_commands() in @cl.on_chat_start
  3. Start a new chat and send a message using a slash command (e.g., /test, /test-command)
  4. Verify the command badge appears next to the user message (showing which command was used)
  5. Switch to a different conversation or reload the page to trigger @cl.on_chat_resume
  6. Return to the original conversation
  7. Observe that the command badge has disappeared from the user message

Expected behavior

Command badges should persist across conversation switches. When resuming a thread via @cl.on_chat_resume, steps loaded from the database should include the command field so the frontend can render the command badge consistently.

Database Evidence

The PostgreSQL database shows that the command field is being saved correctly:

SELECT id, type, command FROM ui.steps WHERE command IS NOT NULL LIMIT 2;

Returns:

                  id                  |     type     |   command    
--------------------------------------+--------------+--------------
 d4e89f12-3456-7890-abcd-ef1234567890 | user_message | test
 a1b2c3d4-5678-90ab-cdef-123456789abc | user_message | test-command

However, when SQLAlchemyDataLayer.get_thread() retrieves and serializes these steps, the command field is not included in the ThreadDict response sent to the frontend.

Frontend Evidence

The React frontend (in chainlit/frontend/dist/assets/index-*.js) has the rendering logic for command badges:

e.command?De.jsx("div",{className:"font-bold text-[#08f] command-span",children:e.command}):null

This confirms the frontend expects a command property on step objects and will render it when present.

Current Workaround

We've implemented a workaround by creating a custom data layer:

class CustomSQLAlchemyDataLayer(SQLAlchemyDataLayer):
    async def get_thread(self, thread_id: str) -> ThreadDict | None:
        thread = await super().get_thread(thread_id)
        if thread and "steps" in thread:
            for step in thread["steps"]:
                metadata = step.get("metadata")
                if metadata and "command" in metadata:
                    step["command"] = metadata["command"]
        return thread

This copies the command from metadata.command (which we store separately) to step["command"] so the frontend receives it.

Environment:

  • OS: macOS
  • Browser: Chrome, Safari (issue occurs on all browsers)
  • Chainlit Version: 2.9.0
  • Python Version: 3.12
  • Database: PostgreSQL (via Docker)
  • Data Layer: SQLAlchemyDataLayer with PostgreSQL backend

Root Cause Analysis

The issue appears to be in chainlit/data/sql_alchemy.py where SQLAlchemyDataLayer.get_thread() serializes step records from the database. The command column exists in the database schema but is not being included in the step dictionary that gets serialized to ThreadDict and sent to the frontend.

Suggested Fix

Update SQLAlchemyDataLayer._record_to_step_dict() or the relevant serialization method to include the command field from the database column when converting step records to dictionaries.

Additional context

  • The Commands API is documented in Chainlit's features, but persistence of command badges across sessions is not working out of the box
  • This affects user experience as users lose context about which specialized agent/command was used for previous messages
  • The database schema correctly includes the command column, suggesting this feature was intended to work but the serialization layer is incomplete
  • Similar issues may exist with other step fields that are stored in the database but not serialized to the frontend

Sanady avatar Nov 14 '25 13:11 Sanady