Show reconnect guidance when disconnecting from tasks (#40629)

## Why

Exiting a TUI connected to a persistent app server can leave work running, so
the existing session-exit summary does not explain how to reconnect or stop the
active turn.

## What changed

- Distinguish disconnects, interrupted turns, and removed threads when building
  exit summaries.
- For daemon and remote sessions, report whether work may still be running and
  print commands to reconnect or stop the current turn.
- Sanitize remote WebSocket addresses in displayed commands and preserve the
  configured remote authentication environment-variable option.
- Keep the existing token-usage and resume summary for embedded sessions.

## Testing

Add coverage for daemon, remote, interrupted, embedded, archived, and deleted
session exits, including credential sanitization.

GitOrigin-RevId: 9cbbf9c7dd36423a5ed99909a2b4cb32e3e87712
This commit is contained in:
Eric Traut authored and copyberry committed 2026-08-25 15:55:35 +00:00
1 parent 0cdb1f1c83
commit a63cb33e8d
16 files changed
+408 -96

No files matched your search

+4 -31
View File
@@ -2,7 +2,6 @@ use clap::Parser;
use codex_arg0::Arg0DispatchPaths;
use codex_arg0::arg0_dispatch_or_else;
use codex_config::LoaderOverrides;
use codex_tui::AppExitInfo;
use codex_tui::Cli;
use codex_tui::ExitReason;
use codex_tui::run_main;
@@ -10,34 +9,6 @@ use codex_utils_cli::CliConfigOverrides;
use std::io::Write;
use supports_color::Stream;
fn format_exit_messages(exit_info: AppExitInfo, color_enabled: bool) -> Vec<String> {
let is_fatal = matches!(&exit_info.exit_reason, ExitReason::Fatal(_));
let AppExitInfo {
token_usage,
thread_id,
resume_hint,
..
} = exit_info;
let mut lines = Vec::new();
if !token_usage.is_zero() {
lines.push(token_usage.to_string());
}
if let Some(resume_cmd) = resume_hint {
let command = if color_enabled {
format!("\u{1b}[36m{resume_cmd}\u{1b}[39m")
} else {
resume_cmd
};
lines.push(format!("To continue this session, run {command}"));
} else if is_fatal && let Some(thread_id) = thread_id {
lines.push(format!("Session ID: {thread_id}"));
}
lines
}
#[derive(Parser, Debug)]
struct TopCli {
#[clap(flatten)]
@@ -67,11 +38,13 @@ fn main() -> anyhow::Result<()> {
eprintln!("ERROR: {message}");
true
}
ExitReason::UserRequested => false,
ExitReason::UserRequested | ExitReason::TurnInterrupted | ExitReason::ThreadRemoved => {
false
}
};
let color_enabled = supports_color::on(Stream::Stdout).is_some();
for line in format_exit_messages(exit_info, color_enabled) {
for line in exit_info.format_exit_messages(color_enabled) {
println!("{line}");
}
if is_fatal {