Skip to content

fix(web): ChatView has no timeout, no retry, and misleading empty state for existing sessions #170

Description

@edspencer

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:

  1. Session appears in sidebar (discovered during a previous cache window)
  2. User clicks session → fetchChatMessages fires
  3. Backend reads JSONL file → returns { messages: [], metadata: {...} } with 200 status
  4. 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

  1. Add an AbortController timeout to fetchChatMessages (e.g., 15 seconds) — show error state with retry button if it times out
  2. 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"
  3. Add a retry button to both the error banner and the empty-existing-session state

Related Issues

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions