Summary
When navigating to an existing chat session that returns no messages (e.g., JSONL file missing, empty, or still being written), the ChatView shows a misleading "Send a message to start the conversation" empty state instead of indicating that something is wrong. Additionally, there is no timeout or retry mechanism on the initial message loading, meaning network issues or slow API responses cause perpetual "Loading messages..." state.
This is separate from but related to issue #164 (attribution cache race condition). Even if #164 is fixed, these frontend gaps remain:
Root Cause Analysis
1. No timeout on fetchChatMessages API call
In packages/web/src/client/src/store/chat-slice.ts (lines 169-192), the fetchChatMessages action calls fetchChatSession() with no timeout. If the API hangs (network issue, proxy timeout, very large JSONL file), the user sees "Loading messages..." indefinitely.
File: /packages/web/src/client/src/store/chat-slice.ts (line 178)
const response = await fetchChatSession(agentName, sessionId);
// No timeout, no AbortController, no retry
2. Misleading empty state for existing sessions
In packages/web/src/client/src/components/chat/MessageFeed.tsx (lines 101-109), when displayMessages.length === 0 && !chatStreaming, the component shows "Send a message to start the conversation." This is correct for new chats but misleading for existing sessions that should have messages.
File: /packages/web/src/client/src/components/chat/MessageFeed.tsx (lines 101-109)
if (displayMessages.length === 0 && !chatStreaming) {
return (
<div className="flex-1 flex items-center justify-center">
<div className="flex flex-col items-center gap-3 text-herd-muted">
<MessageCircle className="w-12 h-12 opacity-50" />
<p className="text-sm">Send a message to start the conversation</p>
</div>
</div>
);
}
3. No retry mechanism
There is no "Retry" button in the error state or loading state. If the initial fetch fails, the user must manually reload the page.
How This Interacts with Issue #164
Issue #164 describes a backend attribution race condition where new sessions aren't discoverable for up to 30 seconds. However, the GET /api/chat/:agentName/sessions/:sessionId endpoint does not use the attribution index — it reads the JSONL file directly from disk. So the attribution bug doesn't cause the API to return errors; instead it returns empty messages if the JSONL file isn't at the expected path.
The sequence for a user hitting this bug:
- Session appears in sidebar (discovered during a previous cache window)
- User clicks session →
fetchChatMessages fires
- Backend reads JSONL file → returns
{ messages: [], metadata: {...} } with 200 status
- Frontend shows "Send a message to start the conversation" — user is confused because they're viewing an existing session that should have messages
Proposed Fix
- Add an AbortController timeout to
fetchChatMessages (e.g., 15 seconds) — show error state with retry button if it times out
- Differentiate empty states:
MessageFeed should accept a prop indicating whether this is an existing session (sessionId is truthy). For existing sessions with 0 messages, show "No messages found for this session" with a Retry button instead of "Send a message to start the conversation"
- Add a retry button to both the error banner and the empty-existing-session state
Related Issues
Summary
When navigating to an existing chat session that returns no messages (e.g., JSONL file missing, empty, or still being written), the ChatView shows a misleading "Send a message to start the conversation" empty state instead of indicating that something is wrong. Additionally, there is no timeout or retry mechanism on the initial message loading, meaning network issues or slow API responses cause perpetual "Loading messages..." state.
This is separate from but related to issue #164 (attribution cache race condition). Even if #164 is fixed, these frontend gaps remain:
Root Cause Analysis
1. No timeout on
fetchChatMessagesAPI callIn
packages/web/src/client/src/store/chat-slice.ts(lines 169-192), thefetchChatMessagesaction callsfetchChatSession()with no timeout. If the API hangs (network issue, proxy timeout, very large JSONL file), the user sees "Loading messages..." indefinitely.File:
/packages/web/src/client/src/store/chat-slice.ts(line 178)2. Misleading empty state for existing sessions
In
packages/web/src/client/src/components/chat/MessageFeed.tsx(lines 101-109), whendisplayMessages.length === 0 && !chatStreaming, the component shows "Send a message to start the conversation." This is correct for new chats but misleading for existing sessions that should have messages.File:
/packages/web/src/client/src/components/chat/MessageFeed.tsx(lines 101-109)3. No retry mechanism
There is no "Retry" button in the error state or loading state. If the initial fetch fails, the user must manually reload the page.
How This Interacts with Issue #164
Issue #164 describes a backend attribution race condition where new sessions aren't discoverable for up to 30 seconds. However, the
GET /api/chat/:agentName/sessions/:sessionIdendpoint does not use the attribution index — it reads the JSONL file directly from disk. So the attribution bug doesn't cause the API to return errors; instead it returns empty messages if the JSONL file isn't at the expected path.The sequence for a user hitting this bug:
fetchChatMessagesfires{ messages: [], metadata: {...} }with 200 statusProposed Fix
fetchChatMessages(e.g., 15 seconds) — show error state with retry button if it times outMessageFeedshould accept a prop indicating whether this is an existing session (sessionIdis truthy). For existing sessions with 0 messages, show "No messages found for this session" with a Retry button instead of "Send a message to start the conversation"Related Issues