Render error state when agent binary exits unexpectedly (#35651)

This PR adds handling for the case where an agent binary exits
unexpectedly after successfully establishing a connection.

Release Notes:

- N/A

---------

Co-authored-by: Agus <agus@zed.dev>
This commit is contained in:
Cole Miller 2025-08-05 18:52:08 -04:00 committed by GitHub
parent 142efbac0d
commit bc2108cbba
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
5 changed files with 116 additions and 35 deletions

View file

@ -5,6 +5,7 @@ use audio::{Audio, Sound};
use std::cell::RefCell;
use std::collections::BTreeMap;
use std::path::Path;
use std::process::ExitStatus;
use std::rc::Rc;
use std::sync::Arc;
use std::time::Duration;
@ -90,6 +91,9 @@ enum ThreadState {
Unauthenticated {
connection: Rc<dyn AgentConnection>,
},
ServerExited {
status: ExitStatus,
},
}
impl AcpThreadView {
@ -229,7 +233,7 @@ impl AcpThreadView {
let connect_task = agent.connect(&root_dir, &project, cx);
let load_task = cx.spawn_in(window, async move |this, cx| {
let connection = match connect_task.await {
Ok(thread) => thread,
Ok(connection) => connection,
Err(err) => {
this.update(cx, |this, cx| {
this.handle_load_error(err, cx);
@ -240,6 +244,20 @@ impl AcpThreadView {
}
};
// this.update_in(cx, |_this, _window, cx| {
// let status = connection.exit_status(cx);
// cx.spawn(async move |this, cx| {
// let status = status.await.ok();
// this.update(cx, |this, cx| {
// this.thread_state = ThreadState::ServerExited { status };
// cx.notify();
// })
// .ok();
// })
// .detach();
// })
// .ok();
let result = match connection
.clone()
.new_thread(project.clone(), &root_dir, cx)
@ -308,7 +326,8 @@ impl AcpThreadView {
ThreadState::Ready { thread, .. } => Some(thread),
ThreadState::Unauthenticated { .. }
| ThreadState::Loading { .. }
| ThreadState::LoadError(..) => None,
| ThreadState::LoadError(..)
| ThreadState::ServerExited { .. } => None,
}
}
@ -318,6 +337,7 @@ impl AcpThreadView {
ThreadState::Loading { .. } => "Loading…".into(),
ThreadState::LoadError(_) => "Failed to load".into(),
ThreadState::Unauthenticated { .. } => "Not authenticated".into(),
ThreadState::ServerExited { .. } => "Server exited unexpectedly".into(),
}
}
@ -647,6 +667,9 @@ impl AcpThreadView {
cx,
);
}
AcpThreadEvent::ServerExited(status) => {
self.thread_state = ThreadState::ServerExited { status: *status };
}
}
cx.notify();
}
@ -1383,7 +1406,29 @@ impl AcpThreadView {
.into_any()
}
fn render_error_state(&self, e: &LoadError, cx: &Context<Self>) -> AnyElement {
fn render_server_exited(&self, status: ExitStatus, _cx: &Context<Self>) -> AnyElement {
v_flex()
.items_center()
.justify_center()
.child(self.render_error_agent_logo())
.child(
v_flex()
.mt_4()
.mb_2()
.gap_0p5()
.text_center()
.items_center()
.child(Headline::new("Server exited unexpectedly").size(HeadlineSize::Medium))
.child(
Label::new(format!("Exit status: {}", status.code().unwrap_or(-127)))
.size(LabelSize::Small)
.color(Color::Muted),
),
)
.into_any_element()
}
fn render_load_error(&self, e: &LoadError, cx: &Context<Self>) -> AnyElement {
let mut container = v_flex()
.items_center()
.justify_center()
@ -2494,7 +2539,13 @@ impl Render for AcpThreadView {
.flex_1()
.items_center()
.justify_center()
.child(self.render_error_state(e, cx)),
.child(self.render_load_error(e, cx)),
ThreadState::ServerExited { status } => v_flex()
.p_2()
.flex_1()
.items_center()
.justify_center()
.child(self.render_server_exited(*status, cx)),
ThreadState::Ready { thread, .. } => {
let thread_clone = thread.clone();

View file

@ -1523,7 +1523,8 @@ impl AgentDiff {
}
AcpThreadEvent::Stopped
| AcpThreadEvent::ToolAuthorizationRequired
| AcpThreadEvent::Error => {}
| AcpThreadEvent::Error
| AcpThreadEvent::ServerExited(_) => {}
}
}