From 2aa245ec0ba058b859c692fec0a30e35f5897a3b Mon Sep 17 00:00:00 2001 From: Droid Agent Date: Tue, 27 Jan 2026 15:39:26 +0000 Subject: [PATCH] fix(tui): display error message when subagent crashes Previously when a subagent task crashed or failed, it was immediately removed from the UI without displaying what error occurred. This made debugging very difficult as users could see the task disappear but had no information about what went wrong. Changes: - Add error_message field to SubagentTaskDisplay to store the error - Update ToolEvent::Failed handler to set the error message and keep the failed subagent visible instead of immediately removing it - Update render_subagent to display the error message in red when status is Failed - Failed subagents are still cleaned up on the next conversation turn via clear_tool_calls which retains only non-terminal subagents --- cortex-tui/src/app.rs | 4 ++++ cortex-tui/src/runner/event_loop.rs | 19 +++++++++++---- cortex-tui/src/views/minimal_session.rs | 31 +++++++++++++++++++++++-- 3 files changed, 48 insertions(+), 6 deletions(-) diff --git a/cortex-tui/src/app.rs b/cortex-tui/src/app.rs index 5a630b15..f07f7006 100644 --- a/cortex-tui/src/app.rs +++ b/cortex-tui/src/app.rs @@ -272,6 +272,9 @@ pub struct SubagentTaskDisplay { pub started_at: Instant, /// Current todo items from the subagent (if any). pub todos: Vec, + /// Error message if the subagent failed. + /// Stored separately so it can be displayed even when the task is removed. + pub error_message: Option, } impl SubagentTaskDisplay { @@ -294,6 +297,7 @@ impl SubagentTaskDisplay { output_preview: String::new(), started_at: Instant::now(), todos: Vec::new(), + error_message: None, } } diff --git a/cortex-tui/src/runner/event_loop.rs b/cortex-tui/src/runner/event_loop.rs index 14f21a7a..9f8d4218 100644 --- a/cortex-tui/src/runner/event_loop.rs +++ b/cortex-tui/src/runner/event_loop.rs @@ -3419,12 +3419,23 @@ impl EventLoop { format!("Error: {}", error), ); - // If this is a Task tool, remove the subagent from display + // If this is a Task tool, update subagent status to Failed with error message + // Keep the subagent visible so users can see what error occurred if name == "Task" || name == "task" { let session_id = format!("subagent_{}", id); - self.app_state.remove_subagent(&session_id); - // Stop delegation mode if no more active subagents - if !self.app_state.has_active_subagents() { + let error_clone = error.clone(); + self.app_state.update_subagent(&session_id, |task| { + task.status = SubagentDisplayStatus::Failed; + task.error_message = Some(error_clone); + task.current_activity = "Failed".to_string(); + }); + // Stop delegation mode if no more active (non-failed) subagents + let has_running_subagents = self + .app_state + .active_subagents + .iter() + .any(|t| !t.status.is_terminal()); + if !has_running_subagents { self.app_state.streaming.stop_delegation(); } } diff --git a/cortex-tui/src/views/minimal_session.rs b/cortex-tui/src/views/minimal_session.rs index 791aff92..09389c7a 100644 --- a/cortex-tui/src/views/minimal_session.rs +++ b/cortex-tui/src/views/minimal_session.rs @@ -413,8 +413,35 @@ impl<'a> MinimalSessionView<'a> { ), ])); - // Display todos if any - use ⎿ prefix for first, space for rest - if !task.todos.is_empty() { + // Display error message if task failed + if task.status == SubagentDisplayStatus::Failed { + if let Some(ref error_msg) = task.error_message { + lines.push(Line::from(vec![ + Span::styled(" ⎿ ", Style::default().fg(self.colors.text_muted)), + Span::styled("Error: ", Style::default().fg(self.colors.error)), + ])); + // Display error message, truncate if too long + for (i, err_line) in error_msg.lines().take(5).enumerate() { + let truncated = if err_line.len() > 70 { + format!("{}...", &err_line.chars().take(67).collect::()) + } else { + err_line.to_string() + }; + let prefix = if i == 0 { " " } else { " " }; + lines.push(Line::from(vec![ + Span::styled(prefix, Style::default().fg(self.colors.text_muted)), + Span::styled(truncated, Style::default().fg(self.colors.error)), + ])); + } + } else { + // Fallback: no error message provided + lines.push(Line::from(vec![ + Span::styled(" ⎿ ", Style::default().fg(self.colors.text_muted)), + Span::styled("Task failed", Style::default().fg(self.colors.error)), + ])); + } + } else if !task.todos.is_empty() { + // Display todos if any - use ⎿ prefix for first, space for rest for (i, todo) in task.todos.iter().enumerate() { let (status_text, status_color) = match todo.status { SubagentTodoStatus::Completed => ("[completed]", self.colors.success),