Retry TUI reconnects while threads are closing (#42207)

## Why

An app-server thread can still be closing when the TUI reconnects. Its
`thread/resume` request then uses the same `-32600` error code as an unavailable
thread, even though the thread can be resumed once closing finishes.

## What changed

Treat the thread-specific "is closing" response as a transient reconnect error
so the existing reconnect loop retries instead of marking the conversation
unavailable.

## Testing

Extend the active reconnect and end-to-end reconnect tests to cover a closing
thread that resumes successfully on the next attempt.

GitOrigin-RevId: 30ce232b8894065fef346a86128b3ff9bf36ba1f
This commit is contained in:
Eric Traut authored and copyberry committed 2026-09-02 05:10:55 +00:00
1 parent 8813bd4b00
commit 27bf160f79
3 files changed
+51 -5

No files matched your search

+16 -3
View File
@@ -36,7 +36,7 @@ async fn automatic_reconnect_restores_draft_and_routes_new_notifications() -> Re
"status": {"type": "active", "activeFlags": []}, "cwd": server_cwd,
"cliVersion": "0.0.0", "source": "cli", "turns": [{"id": "running", "items": [], "status": "inProgress", "error": null}]
});
for connection in 0..2 {
for connection in 0..3 {
let mut socket = loop {
let (stream, _) = listener.accept().await?;
// Startup probes the default daemon socket before opening its WebSocket.
@@ -62,6 +62,19 @@ async fn automatic_reconnect_restores_draft_and_routes_new_notifications() -> Re
if connection == 1 && request.method == "initialize" {
restore_rx.take().unwrap().await?;
}
if connection == 1 && request.method == "thread/resume" {
socket
.send(Message::Text(
json!({"id": request.id, "error": {
"code": -32600,
"message": format!("thread {id} is closing; retry after the thread is closed")
}})
.to_string()
.into(),
))
.await?;
continue;
}
let result = match request.method.as_str() {
"initialize" => json!({"userAgent": "reconnect-pty"}),
"account/read" => {
@@ -97,7 +110,7 @@ async fn automatic_reconnect_restores_draft_and_routes_new_notifications() -> Re
.into(),
))
.await?;
if connection == 1 && request.method == "thread/resume" {
if connection == 2 && request.method == "thread/resume" {
// Keep the recovered turn running: its output must appear without waiting
// for turn/completed or rebuilding the transcript.
socket
@@ -160,7 +173,7 @@ async fn automatic_reconnect_restores_draft_and_routes_new_notifications() -> Re
.iter()
.filter(|method| *method == "thread/resume")
.count(),
1
2
);
assert!(!methods.iter().any(|method| method == "turn/start"));
Ok(())