@@ -41,6 +41,7 @@ use owo_colors::Style;
4141use serde:: Deserialize ;
4242use shlex:: try_join;
4343use std:: collections:: HashMap ;
44+ use std:: io:: IsTerminal ;
4445use std:: io:: Write ;
4546use std:: path:: PathBuf ;
4647use std:: time:: Duration ;
@@ -869,12 +870,17 @@ impl EventProcessor for EventProcessorWithHumanOutput {
869870 ) ;
870871 }
871872
872- // If the user has not piped the final message to a file, they will see
873- // it twice: once written to stderr as part of the normal event
874- // processing, and once here on stdout. We print the token summary above
875- // to help break up the output visually in that case .
873+ // In interactive terminals we already emitted the final assistant
874+ // message on stderr during event processing. Preserve stdout emission
875+ // only for non-interactive use so pipes and scripts still receive the
876+ // final message .
876877 #[ allow( clippy:: print_stdout) ]
877- if let Some ( message) = & self . final_message {
878+ if should_print_final_message_to_stdout (
879+ self . final_message . as_deref ( ) ,
880+ std:: io:: stdout ( ) . is_terminal ( ) ,
881+ std:: io:: stderr ( ) . is_terminal ( ) ,
882+ ) && let Some ( message) = & self . final_message
883+ {
878884 if message. ends_with ( '\n' ) {
879885 print ! ( "{message}" ) ;
880886 } else {
@@ -1025,6 +1031,14 @@ impl EventProcessorWithHumanOutput {
10251031 }
10261032}
10271033
1034+ fn should_print_final_message_to_stdout (
1035+ final_message : Option < & str > ,
1036+ stdout_is_terminal : bool ,
1037+ stderr_is_terminal : bool ,
1038+ ) -> bool {
1039+ final_message. is_some ( ) && !( stdout_is_terminal && stderr_is_terminal)
1040+ }
1041+
10281042struct AgentJobProgressStats {
10291043 processed : usize ,
10301044 total : usize ,
@@ -1192,3 +1206,41 @@ fn format_mcp_invocation(invocation: &McpInvocation) -> String {
11921206 format ! ( "{fq_tool_name}({args_str})" )
11931207 }
11941208}
1209+
1210+ #[ cfg( test) ]
1211+ mod tests {
1212+ use super :: should_print_final_message_to_stdout;
1213+ use pretty_assertions:: assert_eq;
1214+
1215+ #[ test]
1216+ fn suppresses_final_stdout_message_when_both_streams_are_terminals ( ) {
1217+ assert_eq ! (
1218+ should_print_final_message_to_stdout( Some ( "hello" ) , true , true ) ,
1219+ false
1220+ ) ;
1221+ }
1222+
1223+ #[ test]
1224+ fn prints_final_stdout_message_when_stdout_is_not_terminal ( ) {
1225+ assert_eq ! (
1226+ should_print_final_message_to_stdout( Some ( "hello" ) , false , true ) ,
1227+ true
1228+ ) ;
1229+ }
1230+
1231+ #[ test]
1232+ fn prints_final_stdout_message_when_stderr_is_not_terminal ( ) {
1233+ assert_eq ! (
1234+ should_print_final_message_to_stdout( Some ( "hello" ) , true , false ) ,
1235+ true
1236+ ) ;
1237+ }
1238+
1239+ #[ test]
1240+ fn does_not_print_when_message_is_missing ( ) {
1241+ assert_eq ! (
1242+ should_print_final_message_to_stdout( None , false , false ) ,
1243+ false
1244+ ) ;
1245+ }
1246+ }
0 commit comments