Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
64 changes: 64 additions & 0 deletions cortex-tui/src/runner/event_loop.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1422,6 +1422,10 @@ impl EventLoop {
// Update toast notifications (for auto-dismiss with fade)
self.app_state.toasts.tick();

// Check for crashed subagent tasks (panics, cancellations)
// This ensures the main agent always receives a response even if a subagent crashes
self.check_crashed_tasks().await;

// Render frame (respecting frame time to avoid over-rendering)
if self.last_render.elapsed() >= self.min_frame_time {
self.render(terminal)?;
Expand Down Expand Up @@ -3466,6 +3470,66 @@ impl EventLoop {
}
}

/// Checks for crashed background tool tasks (panics or cancelled).
///
/// This method is called periodically to detect subagent tasks that have
/// terminated unexpectedly without sending a completion or failure event.
/// When a crashed task is detected, it sends a ToolEvent::Failed to ensure
/// the main agent always receives a response.
async fn check_crashed_tasks(&mut self) {
// Collect crashed task IDs and their errors
let mut crashed_tasks: Vec<(String, String)> = Vec::new();

// Check all running tool tasks
for (id, handle) in self.running_tool_tasks.iter() {
if handle.is_finished() {
// Task finished - check if it panicked by trying to get the result
// Note: We can't actually await the handle here since we don't own it,
// but is_finished() alone tells us the task ended unexpectedly
// (if it ended normally, it would have sent Completed/Failed events
// which would have removed it from running_tool_tasks)
crashed_tasks.push((
id.clone(),
"Subagent task terminated unexpectedly (possible panic or cancellation)"
.to_string(),
));
}
}

// Process crashed tasks
for (id, error) in crashed_tasks {
tracing::error!("Detected crashed subagent task: {} - {}", id, error);

// Remove from running tasks
if let Some(handle) = self.running_tool_tasks.remove(&id) {
// Try to get the panic message if possible
let error_msg = match handle.await {
Ok(()) => error, // Task completed but didn't send event - shouldn't happen
Err(join_error) => {
if join_error.is_panic() {
format!("Subagent panicked: {:?}", join_error.into_panic())
} else if join_error.is_cancelled() {
"Subagent task was cancelled".to_string()
} else {
format!("Subagent task failed: {}", join_error)
}
}
};

// Send failure event through the tool event channel
let _ = self
.tool_event_tx
.send(ToolEvent::Failed {
id: id.clone(),
name: "Task".to_string(),
error: error_msg,
duration: std::time::Duration::from_secs(0),
})
.await;
}
}
}

// ========================================================================
// SUBAGENT EVENT HANDLING
// ========================================================================
Expand Down
Loading