diff --git a/coderd/chatd/chatd.go b/coderd/chatd/chatd.go index b74061a128..abd97dfb63 100644 --- a/coderd/chatd/chatd.go +++ b/coderd/chatd/chatd.go @@ -478,6 +478,7 @@ func (p *Server) EditMessage( WorkerID: uuid.NullUUID{}, StartedAt: sql.NullTime{}, HeartbeatAt: sql.NullTime{}, + LastError: sql.NullString{}, }) if err != nil { return xerrors.Errorf("set chat pending: %w", err) @@ -739,6 +740,7 @@ func setChatPendingWithStore( WorkerID: uuid.NullUUID{}, StartedAt: sql.NullTime{}, HeartbeatAt: sql.NullTime{}, + LastError: sql.NullString{}, }) if err != nil { return database.Chat{}, xerrors.Errorf("set chat pending: %w", err) @@ -753,6 +755,7 @@ func (p *Server) setChatWaiting(ctx context.Context, chatID uuid.UUID) (database WorkerID: uuid.NullUUID{}, StartedAt: sql.NullTime{}, HeartbeatAt: sql.NullTime{}, + LastError: sql.NullString{}, }) if err != nil { return database.Chat{}, err @@ -810,6 +813,7 @@ func insertUserMessageAndSetPending( WorkerID: uuid.NullUUID{}, StartedAt: sql.NullTime{}, HeartbeatAt: sql.NullTime{}, + LastError: sql.NullString{}, }) if err != nil { return database.ChatMessage{}, database.Chat{}, xerrors.Errorf("set chat pending: %w", err) @@ -1622,8 +1626,9 @@ func (p *Server) processChat(ctx context.Context, chat database.Chat) { Valid: true, }) - // Determine the final status to set when we're done. + // Determine the final status and last error to set when we're done. status := database.ChatStatusWaiting + lastError := "" remainingQueuedMessages := []database.ChatQueuedMessage{} shouldPublishQueueUpdate := false @@ -1636,7 +1641,8 @@ func (p *Server) processChat(ctx context.Context, chat database.Chat) { // Handle panics gracefully. if r := recover(); r != nil { logger.Error(cleanupCtx, "panic during chat processing", slog.F("panic", r)) - p.publishError(chat.ID, panicFailureReason(r)) + lastError = panicFailureReason(r) + p.publishError(chat.ID, lastError) status = database.ChatStatusError } @@ -1707,6 +1713,7 @@ func (p *Server) processChat(ctx context.Context, chat database.Chat) { WorkerID: uuid.NullUUID{}, StartedAt: sql.NullTime{}, HeartbeatAt: sql.NullTime{}, + LastError: sql.NullString{String: lastError, Valid: lastError != ""}, }) return updateErr }, nil) @@ -1746,7 +1753,8 @@ func (p *Server) processChat(ctx context.Context, chat database.Chat) { } logger.Error(ctx, "failed to process chat", slog.Error(err)) if reason, ok := processingFailureReason(err); ok { - p.publishError(chat.ID, reason) + lastError = reason + p.publishError(chat.ID, lastError) } status = database.ChatStatusError return @@ -2458,6 +2466,7 @@ func (p *Server) recoverStaleChats(ctx context.Context) { WorkerID: uuid.NullUUID{}, StartedAt: sql.NullTime{}, HeartbeatAt: sql.NullTime{}, + LastError: sql.NullString{}, }) if err != nil { p.logger.Error(ctx, "failed to recover stale chat", diff --git a/coderd/chatd/chatd_test.go b/coderd/chatd/chatd_test.go index 2f691df79b..2c1dbb33c0 100644 --- a/coderd/chatd/chatd_test.go +++ b/coderd/chatd/chatd_test.go @@ -577,6 +577,61 @@ func TestWaitingChatsAreNotRecoveredAsStale(t *testing.T) { "waiting chat should not be modified by stale recovery") } +func TestUpdateChatStatusPersistsLastError(t *testing.T) { + t.Parallel() + + db, ps := dbtestutil.NewDB(t) + _ = newTestServer(t, db, ps, uuid.New()) + + ctx := testutil.Context(t, testutil.WaitLong) + user, model := seedChatDependencies(ctx, t, db) + + chat, err := db.InsertChat(ctx, database.InsertChatParams{ + OwnerID: user.ID, + Title: "error-persisted", + LastModelConfigID: model.ID, + }) + require.NoError(t, err) + + // Simulate a chat that failed with an error. + errorMessage := "stream response: status 500: internal server error" + chat, err = db.UpdateChatStatus(ctx, database.UpdateChatStatusParams{ + ID: chat.ID, + Status: database.ChatStatusError, + WorkerID: uuid.NullUUID{}, + StartedAt: sql.NullTime{}, + HeartbeatAt: sql.NullTime{}, + LastError: sql.NullString{String: errorMessage, Valid: true}, + }) + require.NoError(t, err) + require.Equal(t, database.ChatStatusError, chat.Status) + require.Equal(t, sql.NullString{String: errorMessage, Valid: true}, chat.LastError) + + // Verify the error is persisted when re-read from the database. + fromDB, err := db.GetChatByID(ctx, chat.ID) + require.NoError(t, err) + require.Equal(t, database.ChatStatusError, fromDB.Status) + require.Equal(t, sql.NullString{String: errorMessage, Valid: true}, fromDB.LastError) + + // Verify the error is cleared when the chat transitions to a + // non-error status (e.g. pending after a retry). + chat, err = db.UpdateChatStatus(ctx, database.UpdateChatStatusParams{ + ID: chat.ID, + Status: database.ChatStatusPending, + WorkerID: uuid.NullUUID{}, + StartedAt: sql.NullTime{}, + HeartbeatAt: sql.NullTime{}, + LastError: sql.NullString{}, + }) + require.NoError(t, err) + require.Equal(t, database.ChatStatusPending, chat.Status) + require.False(t, chat.LastError.Valid) + + fromDB, err = db.GetChatByID(ctx, chat.ID) + require.NoError(t, err) + require.False(t, fromDB.LastError.Valid) +} + func newTestServer( t *testing.T, db database.Store, diff --git a/coderd/chats.go b/coderd/chats.go index c09e5e8d04..c741300d63 100644 --- a/coderd/chats.go +++ b/coderd/chats.go @@ -795,6 +795,7 @@ func (api *API) interruptChat(rw http.ResponseWriter, r *http.Request) { WorkerID: uuid.NullUUID{}, StartedAt: sql.NullTime{}, HeartbeatAt: sql.NullTime{}, + LastError: sql.NullString{}, }) if updateErr != nil { api.Logger.Error(ctx, "failed to mark chat as waiting", @@ -2059,6 +2060,9 @@ func convertChat(c database.Chat, diffStatus *database.ChatDiffStatus) codersdk. CreatedAt: c.CreatedAt, UpdatedAt: c.UpdatedAt, } + if c.LastError.Valid { + chat.LastError = &c.LastError.String + } if c.ParentChatID.Valid { parentChatID := c.ParentChatID.UUID chat.ParentChatID = &parentChatID diff --git a/coderd/database/dump.sql b/coderd/database/dump.sql index 83c80a425d..2a9802b253 100644 --- a/coderd/database/dump.sql +++ b/coderd/database/dump.sql @@ -1274,7 +1274,8 @@ CREATE TABLE chats ( parent_chat_id uuid, root_chat_id uuid, last_model_config_id uuid NOT NULL, - archived boolean DEFAULT false NOT NULL + archived boolean DEFAULT false NOT NULL, + last_error text ); CREATE TABLE connection_logs ( diff --git a/coderd/database/migrations/000424_chat_last_error.down.sql b/coderd/database/migrations/000424_chat_last_error.down.sql new file mode 100644 index 0000000000..7372dc532c --- /dev/null +++ b/coderd/database/migrations/000424_chat_last_error.down.sql @@ -0,0 +1 @@ +ALTER TABLE chats DROP COLUMN last_error; diff --git a/coderd/database/migrations/000424_chat_last_error.up.sql b/coderd/database/migrations/000424_chat_last_error.up.sql new file mode 100644 index 0000000000..4bdd82fdc4 --- /dev/null +++ b/coderd/database/migrations/000424_chat_last_error.up.sql @@ -0,0 +1 @@ +ALTER TABLE chats ADD COLUMN last_error TEXT; diff --git a/coderd/database/models.go b/coderd/database/models.go index 8fc7649a5f..45db1db2ab 100644 --- a/coderd/database/models.go +++ b/coderd/database/models.go @@ -3886,21 +3886,22 @@ type BoundaryUsageStat struct { } type Chat struct { - ID uuid.UUID `db:"id" json:"id"` - OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` - WorkspaceID uuid.NullUUID `db:"workspace_id" json:"workspace_id"` - WorkspaceAgentID uuid.NullUUID `db:"workspace_agent_id" json:"workspace_agent_id"` - Title string `db:"title" json:"title"` - Status ChatStatus `db:"status" json:"status"` - WorkerID uuid.NullUUID `db:"worker_id" json:"worker_id"` - StartedAt sql.NullTime `db:"started_at" json:"started_at"` - HeartbeatAt sql.NullTime `db:"heartbeat_at" json:"heartbeat_at"` - CreatedAt time.Time `db:"created_at" json:"created_at"` - UpdatedAt time.Time `db:"updated_at" json:"updated_at"` - ParentChatID uuid.NullUUID `db:"parent_chat_id" json:"parent_chat_id"` - RootChatID uuid.NullUUID `db:"root_chat_id" json:"root_chat_id"` - LastModelConfigID uuid.UUID `db:"last_model_config_id" json:"last_model_config_id"` - Archived bool `db:"archived" json:"archived"` + ID uuid.UUID `db:"id" json:"id"` + OwnerID uuid.UUID `db:"owner_id" json:"owner_id"` + WorkspaceID uuid.NullUUID `db:"workspace_id" json:"workspace_id"` + WorkspaceAgentID uuid.NullUUID `db:"workspace_agent_id" json:"workspace_agent_id"` + Title string `db:"title" json:"title"` + Status ChatStatus `db:"status" json:"status"` + WorkerID uuid.NullUUID `db:"worker_id" json:"worker_id"` + StartedAt sql.NullTime `db:"started_at" json:"started_at"` + HeartbeatAt sql.NullTime `db:"heartbeat_at" json:"heartbeat_at"` + CreatedAt time.Time `db:"created_at" json:"created_at"` + UpdatedAt time.Time `db:"updated_at" json:"updated_at"` + ParentChatID uuid.NullUUID `db:"parent_chat_id" json:"parent_chat_id"` + RootChatID uuid.NullUUID `db:"root_chat_id" json:"root_chat_id"` + LastModelConfigID uuid.UUID `db:"last_model_config_id" json:"last_model_config_id"` + Archived bool `db:"archived" json:"archived"` + LastError sql.NullString `db:"last_error" json:"last_error"` } type ChatDiffStatus struct { diff --git a/coderd/database/queries.sql.go b/coderd/database/queries.sql.go index 6c30113fb3..15c37c8391 100644 --- a/coderd/database/queries.sql.go +++ b/coderd/database/queries.sql.go @@ -2842,7 +2842,7 @@ WHERE 1 ) RETURNING - id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived + id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error ` type AcquireChatParams struct { @@ -2871,6 +2871,7 @@ func (q *sqlQuerier) AcquireChat(ctx context.Context, arg AcquireChatParams) (Ch &i.RootChatID, &i.LastModelConfigID, &i.Archived, + &i.LastError, ) return i, err } @@ -2939,7 +2940,7 @@ func (q *sqlQuerier) DeleteChatQueuedMessage(ctx context.Context, arg DeleteChat const getChatByID = `-- name: GetChatByID :one SELECT - id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived + id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error FROM chats WHERE @@ -2965,12 +2966,13 @@ func (q *sqlQuerier) GetChatByID(ctx context.Context, id uuid.UUID) (Chat, error &i.RootChatID, &i.LastModelConfigID, &i.Archived, + &i.LastError, ) return i, err } const getChatByIDForUpdate = `-- name: GetChatByIDForUpdate :one -SELECT id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived FROM chats WHERE id = $1::uuid FOR UPDATE +SELECT id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error FROM chats WHERE id = $1::uuid FOR UPDATE ` func (q *sqlQuerier) GetChatByIDForUpdate(ctx context.Context, id uuid.UUID) (Chat, error) { @@ -2992,6 +2994,7 @@ func (q *sqlQuerier) GetChatByIDForUpdate(ctx context.Context, id uuid.UUID) (Ch &i.RootChatID, &i.LastModelConfigID, &i.Archived, + &i.LastError, ) return i, err } @@ -3288,7 +3291,7 @@ func (q *sqlQuerier) GetChatQueuedMessages(ctx context.Context, chatID uuid.UUID const getChatsByOwnerID = `-- name: GetChatsByOwnerID :many SELECT - id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived + id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error FROM chats WHERE @@ -3323,6 +3326,7 @@ func (q *sqlQuerier) GetChatsByOwnerID(ctx context.Context, ownerID uuid.UUID) ( &i.RootChatID, &i.LastModelConfigID, &i.Archived, + &i.LastError, ); err != nil { return nil, err } @@ -3339,7 +3343,7 @@ func (q *sqlQuerier) GetChatsByOwnerID(ctx context.Context, ownerID uuid.UUID) ( const getStaleChats = `-- name: GetStaleChats :many SELECT - id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived + id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error FROM chats WHERE @@ -3374,6 +3378,7 @@ func (q *sqlQuerier) GetStaleChats(ctx context.Context, staleThreshold time.Time &i.RootChatID, &i.LastModelConfigID, &i.Archived, + &i.LastError, ); err != nil { return nil, err } @@ -3407,7 +3412,7 @@ INSERT INTO chats ( $7::text ) RETURNING - id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived + id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error ` type InsertChatParams struct { @@ -3447,6 +3452,7 @@ func (q *sqlQuerier) InsertChat(ctx context.Context, arg InsertChatParams) (Chat &i.RootChatID, &i.LastModelConfigID, &i.Archived, + &i.LastError, ) return i, err } @@ -3572,7 +3578,7 @@ func (q *sqlQuerier) InsertChatQueuedMessage(ctx context.Context, arg InsertChat const listChatsByRootID = `-- name: ListChatsByRootID :many SELECT - id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived + id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error FROM chats WHERE @@ -3606,6 +3612,7 @@ func (q *sqlQuerier) ListChatsByRootID(ctx context.Context, rootChatID uuid.UUID &i.RootChatID, &i.LastModelConfigID, &i.Archived, + &i.LastError, ); err != nil { return nil, err } @@ -3622,7 +3629,7 @@ func (q *sqlQuerier) ListChatsByRootID(ctx context.Context, rootChatID uuid.UUID const listChildChatsByParentID = `-- name: ListChildChatsByParentID :many SELECT - id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived + id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error FROM chats WHERE @@ -3656,6 +3663,7 @@ func (q *sqlQuerier) ListChildChatsByParentID(ctx context.Context, parentChatID &i.RootChatID, &i.LastModelConfigID, &i.Archived, + &i.LastError, ); err != nil { return nil, err } @@ -3711,7 +3719,7 @@ SET WHERE id = $2::uuid RETURNING - id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived + id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error ` type UpdateChatByIDParams struct { @@ -3738,6 +3746,7 @@ func (q *sqlQuerier) UpdateChatByID(ctx context.Context, arg UpdateChatByIDParam &i.RootChatID, &i.LastModelConfigID, &i.Archived, + &i.LastError, ) return i, err } @@ -3817,19 +3826,21 @@ SET worker_id = $2::uuid, started_at = $3::timestamptz, heartbeat_at = $4::timestamptz, + last_error = $5::text, updated_at = NOW() WHERE - id = $5::uuid + id = $6::uuid RETURNING - id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived + id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error ` type UpdateChatStatusParams struct { - Status ChatStatus `db:"status" json:"status"` - WorkerID uuid.NullUUID `db:"worker_id" json:"worker_id"` - StartedAt sql.NullTime `db:"started_at" json:"started_at"` - HeartbeatAt sql.NullTime `db:"heartbeat_at" json:"heartbeat_at"` - ID uuid.UUID `db:"id" json:"id"` + Status ChatStatus `db:"status" json:"status"` + WorkerID uuid.NullUUID `db:"worker_id" json:"worker_id"` + StartedAt sql.NullTime `db:"started_at" json:"started_at"` + HeartbeatAt sql.NullTime `db:"heartbeat_at" json:"heartbeat_at"` + LastError sql.NullString `db:"last_error" json:"last_error"` + ID uuid.UUID `db:"id" json:"id"` } func (q *sqlQuerier) UpdateChatStatus(ctx context.Context, arg UpdateChatStatusParams) (Chat, error) { @@ -3838,6 +3849,7 @@ func (q *sqlQuerier) UpdateChatStatus(ctx context.Context, arg UpdateChatStatusP arg.WorkerID, arg.StartedAt, arg.HeartbeatAt, + arg.LastError, arg.ID, ) var i Chat @@ -3857,6 +3869,7 @@ func (q *sqlQuerier) UpdateChatStatus(ctx context.Context, arg UpdateChatStatusP &i.RootChatID, &i.LastModelConfigID, &i.Archived, + &i.LastError, ) return i, err } @@ -3871,7 +3884,7 @@ SET WHERE id = $3::uuid RETURNING - id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived + id, owner_id, workspace_id, workspace_agent_id, title, status, worker_id, started_at, heartbeat_at, created_at, updated_at, parent_chat_id, root_chat_id, last_model_config_id, archived, last_error ` type UpdateChatWorkspaceParams struct { @@ -3899,6 +3912,7 @@ func (q *sqlQuerier) UpdateChatWorkspace(ctx context.Context, arg UpdateChatWork &i.RootChatID, &i.LastModelConfigID, &i.Archived, + &i.LastError, ) return i, err } diff --git a/coderd/database/queries/chats.sql b/coderd/database/queries/chats.sql index 890849e3a6..ce1cc12db0 100644 --- a/coderd/database/queries/chats.sql +++ b/coderd/database/queries/chats.sql @@ -266,6 +266,7 @@ SET worker_id = sqlc.narg('worker_id')::uuid, started_at = sqlc.narg('started_at')::timestamptz, heartbeat_at = sqlc.narg('heartbeat_at')::timestamptz, + last_error = sqlc.narg('last_error')::text, updated_at = NOW() WHERE id = @id::uuid diff --git a/codersdk/chats.go b/codersdk/chats.go index 3da8592df7..e6a141b8d8 100644 --- a/codersdk/chats.go +++ b/codersdk/chats.go @@ -38,6 +38,7 @@ type Chat struct { LastModelConfigID uuid.UUID `json:"last_model_config_id" format:"uuid"` Title string `json:"title"` Status ChatStatus `json:"status"` + LastError *string `json:"last_error"` DiffStatus *ChatDiffStatus `json:"diff_status,omitempty"` CreatedAt time.Time `json:"created_at" format:"date-time"` UpdatedAt time.Time `json:"updated_at" format:"date-time"` diff --git a/site/src/api/typesGenerated.ts b/site/src/api/typesGenerated.ts index eb9e119eb6..d881a91a8f 100644 --- a/site/src/api/typesGenerated.ts +++ b/site/src/api/typesGenerated.ts @@ -1060,6 +1060,7 @@ export interface Chat { readonly last_model_config_id: string; readonly title: string; readonly status: ChatStatus; + readonly last_error: string | null; readonly diff_status?: ChatDiffStatus; readonly created_at: string; readonly updated_at: string; diff --git a/site/src/pages/AgentsPage/AgentDetail.stories.tsx b/site/src/pages/AgentsPage/AgentDetail.stories.tsx index ae3b9ed77c..dfe5bf96b1 100644 --- a/site/src/pages/AgentsPage/AgentDetail.stories.tsx +++ b/site/src/pages/AgentsPage/AgentDetail.stories.tsx @@ -122,6 +122,7 @@ const baseChatFields = { created_at: "2026-02-18T00:00:00.000Z", updated_at: "2026-02-18T00:00:00.000Z", archived: false, + last_error: null, } as const; // --------------------------------------------------------------------------- diff --git a/site/src/pages/AgentsPage/AgentDetail.tsx b/site/src/pages/AgentsPage/AgentDetail.tsx index 518b71ca92..5d8801bf12 100644 --- a/site/src/pages/AgentsPage/AgentDetail.tsx +++ b/site/src/pages/AgentsPage/AgentDetail.tsx @@ -974,7 +974,9 @@ const AgentDetail: FC = () => { ({ created_at: "2025-01-01T00:00:00.000Z", updated_at: "2025-01-01T00:00:00.000Z", archived: false, + last_error: null, }); const makeMessage = ( diff --git a/site/src/pages/AgentsPage/AgentsSidebar.stories.tsx b/site/src/pages/AgentsPage/AgentsSidebar.stories.tsx index 18ee765d4f..275e64c30b 100644 --- a/site/src/pages/AgentsPage/AgentsSidebar.stories.tsx +++ b/site/src/pages/AgentsPage/AgentsSidebar.stories.tsx @@ -39,6 +39,7 @@ const buildChat = (overrides: Partial = {}): Chat => ({ created_at: "2026-02-18T00:00:00.000Z", updated_at: "2026-02-18T00:00:00.000Z", archived: false, + last_error: null, ...overrides, }); diff --git a/site/src/pages/AgentsPage/AgentsSidebar.tsx b/site/src/pages/AgentsPage/AgentsSidebar.tsx index 3200f3785b..e5ed8e44b6 100644 --- a/site/src/pages/AgentsPage/AgentsSidebar.tsx +++ b/site/src/pages/AgentsPage/AgentsSidebar.tsx @@ -312,7 +312,9 @@ const ChatTreeNode = memo(({ chat, isChildNode }) => { modelOptions, ); const errorReason = - chat.status === "error" ? chatErrorReasons[chat.id] : undefined; + chat.status === "error" + ? chatErrorReasons[chat.id] || chat.last_error || undefined + : undefined; const subtitle = errorReason || modelName; const diffStatus = getChatDiffStatus(chat); const hasLinkedDiffStatus = Boolean(diffStatus?.url);