@@ -159,6 +159,7 @@ static bool daemon_spawn_worker(Test_Daemon_Server *server,
159159 uint64_t request_id );
160160static bool daemon_finish_active_run (Test_Daemon_Server * server );
161161static void daemon_handle_client_disconnect (Test_Daemon_Server * server );
162+ static bool daemon_watch_send_infof (Test_Daemon_Server * server , const char * fmt , ...);
162163static void daemon_watch_reset_session (Test_Daemon_Server * server );
163164static bool daemon_begin_worker_cancel (Test_Daemon_Server * server ,
164165 bool mark_watch_cancel ,
@@ -252,6 +253,10 @@ static bool daemon_flush_buffered_worker_output(Test_Daemon_Server *server) {
252253 bool ok = true;
253254
254255 if (!server ) return false;
256+ if (server -> watch .active && server -> current_client_fd < 0 ) {
257+ daemon_clear_buffered_worker_output (server );
258+ return true;
259+ }
255260 if (server -> buffered_stderr .count > 0 ) {
256261 ok = test_daemon_send_message (server -> current_client_fd ,
257262 TEST_DAEMON_MESSAGE_STDERR ,
@@ -551,16 +556,42 @@ static void daemon_remove_state_files(Test_Daemon_Server *server, bool record_ex
551556 }
552557}
553558
559+ static bool daemon_watch_has_attached_client (const Test_Daemon_Server * server ) {
560+ return server && server -> watch .active && server -> current_client_fd >= 0 ;
561+ }
562+
563+ static const char * daemon_watch_status_summary (const Test_Daemon_Server * server ) {
564+ if (!server ) return "watching" ;
565+ return daemon_watch_has_attached_client (server ) ? "watching (attached)" : "watching (detached)" ;
566+ }
567+
568+ static void daemon_watch_detach_client (Test_Daemon_Server * server , const char * reason ) {
569+ if (!server || !server -> watch .active ) return ;
570+ server -> watch .client_source = sd_event_source_unref (server -> watch .client_source );
571+ daemon_close_fd (& server -> current_client_fd );
572+ if (reason && reason [0 ] != '\0' ) {
573+ daemon_set_status_detail (server , "%s" , reason );
574+ } else {
575+ daemon_set_status_detail (server , "watch session detached from its originating client" );
576+ }
577+ }
578+
554579static bool daemon_watch_send_text (Test_Daemon_Server * server , Test_Daemon_Message_Type type , const char * text ) {
555580 uint64_t request_id = 0 ;
556581
557- if (!server || server -> current_client_fd < 0 || !text ) return false;
582+ if (!server || !text ) return false;
583+ if (server -> watch .active && server -> current_client_fd < 0 ) return true;
584+ if (server -> current_client_fd < 0 ) return false;
558585 request_id = server -> watch .active ? server -> watch .request_id : server -> current_request_id ;
559586 if (!test_daemon_send_message (server -> current_client_fd ,
560587 type ,
561588 request_id ,
562589 text ,
563590 (uint32_t )strlen (text ))) {
591+ if (server -> watch .active ) {
592+ daemon_watch_detach_client (server , "watch session detached after client connection closed" );
593+ return true;
594+ }
564595 daemon_handle_client_disconnect (server );
565596 return false;
566597 }
@@ -569,16 +600,31 @@ static bool daemon_watch_send_text(Test_Daemon_Server *server, Test_Daemon_Messa
569600
570601static void daemon_handle_client_disconnect (Test_Daemon_Server * server ) {
571602 if (!server ) return ;
572- daemon_close_fd (& server -> current_client_fd );
573603 if (server -> watch .active ) {
574- if (server -> worker_pid > 0 ) {
575- (void )daemon_begin_worker_cancel (server , true, true);
576- } else {
577- daemon_watch_reset_session (server );
578- }
604+ daemon_watch_detach_client (server , "watch session detached after client disconnect" );
605+ return ;
606+ }
607+ daemon_close_fd (& server -> current_client_fd );
608+ if (server -> worker_pid > 0 ) {
609+ daemon_set_status_detail (server , "foreground client disconnected while a run was active" );
579610 }
580611}
581612
613+ static bool daemon_watch_report_issue (Test_Daemon_Server * server , const char * fmt , ...) {
614+ char buffer [TEST_RUNNER_SUMMARY_CAPACITY * 2 ] = {0 };
615+ va_list args ;
616+ int n = 0 ;
617+
618+ if (!server || !fmt ) return false;
619+ va_start (args , fmt );
620+ n = vsnprintf (buffer , sizeof (buffer ), fmt , args );
621+ va_end (args );
622+ if (n < 0 || (size_t )n >= sizeof (buffer )) return false;
623+ daemon_set_status_detail (server , "%s" , buffer );
624+ nob_log (NOB_ERROR , "[watch] %s" , buffer );
625+ return daemon_watch_send_infof (server , "[watch] %s\n" , buffer );
626+ }
627+
582628static bool daemon_watch_send_infof (Test_Daemon_Server * server , const char * fmt , ...) {
583629 char buffer [TEST_RUNNER_SUMMARY_CAPACITY * 2 ] = {0 };
584630 va_list args ;
@@ -1352,24 +1398,23 @@ static bool daemon_watch_start_next_module(Test_Daemon_Server *server) {
13521398
13531399 daemon_reset_request_telemetry (server );
13541400 if (!daemon_ensure_preflight (server , request .profile_id )) {
1355- return daemon_send_structured_error (server ,
1356- server -> current_client_fd ,
1357- server -> watch .request_id ,
1358- TEST_DAEMON_ERROR_PREFLIGHT_FAILED ,
1359- "daemon preflight failed" );
1401+ daemon_watch_clear_batch (server );
1402+ return daemon_watch_report_issue (server , "daemon preflight failed; watch session remains active" );
13601403 }
1404+ daemon_set_status_detail (server ,
1405+ "%s running %s (%s)" ,
1406+ daemon_watch_has_attached_client (server ) ? "watch session" : "detached watch session" ,
1407+ module -> name ,
1408+ profile -> name );
13611409 if (!daemon_watch_send_infof (server ,
13621410 "[watch] running %s (%s)\n" ,
13631411 module -> name ,
13641412 profile -> name )) {
13651413 return false;
13661414 }
13671415 if (!daemon_spawn_worker (server , & request , server -> watch .request_id )) {
1368- return daemon_send_structured_error (server ,
1369- server -> current_client_fd ,
1370- server -> watch .request_id ,
1371- TEST_DAEMON_ERROR_WORKER_START_FAILED ,
1372- "watch failed to start worker" );
1416+ daemon_watch_clear_batch (server );
1417+ return daemon_watch_report_issue (server , "watch failed to start worker; session remains active" );
13731418 }
13741419 return true;
13751420}
@@ -1385,12 +1430,7 @@ static int daemon_on_watch_client(sd_event_source *source,
13851430 if (!(revents & (EPOLLHUP | EPOLLERR | EPOLLRDHUP ))) return 0 ;
13861431 if (!server || !server -> watch .active ) return 0 ;
13871432
1388- if (server -> worker_pid > 0 ) {
1389- (void )daemon_begin_worker_cancel (server , true, true);
1390- } else {
1391- daemon_watch_reset_session (server );
1392- if (server -> shutting_down ) (void )sd_event_exit (server -> event , 0 );
1393- }
1433+ daemon_watch_detach_client (server , "watch session detached after client connection closed" );
13941434 return 0 ;
13951435}
13961436
@@ -1408,6 +1448,9 @@ static int daemon_on_watch_debounce(sd_event_source *source,
14081448
14091449 if (server -> worker_pid > 0 ) {
14101450 server -> watch .rerun_pending = true;
1451+ daemon_set_status_detail (server ,
1452+ "%s queued rerun after newer changes" ,
1453+ daemon_watch_has_attached_client (server ) ? "watch session" : "detached watch session" );
14111454 if (!server -> watch .cancel_requested ) {
14121455 (void )daemon_watch_send_infof (server , "[watch] canceling stale run after newer changes\n" );
14131456 }
@@ -1530,6 +1573,7 @@ static bool daemon_watch_start_session(Test_Daemon_Server *server,
15301573 server -> current_worker_request_valid = false;
15311574 server -> current_policy = TEST_DAEMON_POLICY_WATCH_REPLACE_RUNNING ;
15321575 server -> state = TEST_DAEMON_STATE_WATCHING ;
1576+ daemon_set_status_detail (server , "watch session starting" );
15331577
15341578 if (!daemon_watch_register_roots (server )) goto defer ;
15351579 if (sd_event_add_io (server -> event ,
@@ -1539,17 +1583,31 @@ static bool daemon_watch_start_session(Test_Daemon_Server *server,
15391583 daemon_on_watch_client ,
15401584 server ) < 0 ) {
15411585 nob_log (NOB_ERROR , "daemon failed to watch active client connection" );
1542- goto defer ;
1586+ daemon_watch_detach_client ( server , "watch session detached because client monitoring could not be installed" ) ;
15431587 }
15441588
1545- if (!test_daemon_send_message (fd , TEST_DAEMON_MESSAGE_ACK , message -> header .request_id , NULL , 0 )) goto defer ;
1589+ if (server -> current_client_fd >= 0 &&
1590+ !test_daemon_send_message (server -> current_client_fd ,
1591+ TEST_DAEMON_MESSAGE_ACK ,
1592+ message -> header .request_id ,
1593+ NULL ,
1594+ 0 )) {
1595+ daemon_watch_detach_client (server , "watch session detached before the startup ACK completed" );
1596+ }
15461597 if (request .mode == TEST_RUNNER_WATCH_MODE_AUTO ) {
1598+ daemon_set_status_detail (server ,
1599+ "watch session started for auto routing (%s)" ,
1600+ daemon_watch_has_attached_client (server ) ? "attached" : "detached" );
15471601 if (!daemon_watch_send_infof (server , "[watch] auto session started (%zu roots)\n" , server -> watch .roots .count )) {
15481602 goto defer ;
15491603 }
15501604 } else {
15511605 const Test_Runner_Module_Def * module = test_runner_get_module_def (request .module_id );
15521606 if (!module ) goto defer ;
1607+ daemon_set_status_detail (server ,
1608+ "watch session started for %s (%s)" ,
1609+ module -> name ,
1610+ daemon_watch_has_attached_client (server ) ? "attached" : "detached" );
15531611 if (!daemon_watch_send_infof (server ,
15541612 "[watch] module session started for %s (%zu roots)\n" ,
15551613 module -> name ,
@@ -1648,17 +1706,22 @@ static bool daemon_finish_active_run(Test_Daemon_Server *server) {
16481706 bool ok = false;
16491707 bool watch_active = false;
16501708 bool close_client_fd = false;
1709+ bool have_attached_client = false;
16511710
1652- if (!server || server -> current_client_fd < 0 ) return false;
1711+ if (!server ) return false;
16531712 if (!server -> worker_exited || !server -> stdout_closed || !server -> stderr_closed ) return true;
16541713 watch_active = server -> watch .active ;
1714+ have_attached_client = server -> current_client_fd >= 0 ;
16551715
16561716 if (watch_active && server -> watch .cancel_requested ) {
16571717 if (!daemon_watch_send_infof (server , "[watch] stale run aborted after newer changes\n" )) {
16581718 daemon_watch_reset_session (server );
16591719 return false;
16601720 }
16611721 if (server -> worker_result_path [0 ] != '\0' ) (void )unlink (server -> worker_result_path );
1722+ daemon_set_status_detail (server ,
1723+ "%s canceled a stale run after newer changes" ,
1724+ daemon_watch_has_attached_client (server ) ? "watch session" : "detached watch session" );
16621725 server -> watch .cancel_requested = false;
16631726 close_client_fd = server -> watch .closing ;
16641727 daemon_reset_worker_state (server , close_client_fd );
@@ -1702,6 +1765,17 @@ static bool daemon_finish_active_run(Test_Daemon_Server *server) {
17021765 server -> last_result = runner_result ;
17031766 server -> last_result_valid = true;
17041767 server -> last_telemetry = server -> current_telemetry ;
1768+ if (watch_active ) {
1769+ daemon_set_status_detail (server ,
1770+ "%s last result: %s" ,
1771+ daemon_watch_has_attached_client (server ) ? "watch session" : "detached watch session" ,
1772+ runner_result .summary [0 ] != '\0' ? runner_result .summary : (runner_result .ok ? "PASS" : "FAIL" ));
1773+ } else if (!have_attached_client ) {
1774+ daemon_set_status_detail (server ,
1775+ "foreground run completed after client disconnect: %s" ,
1776+ runner_result .summary [0 ] != '\0' ? runner_result .summary
1777+ : (runner_result .ok ? "PASS" : "FAIL" ));
1778+ }
17051779
17061780 if (!test_daemon_result_from_runner (daemon_effective_state (server ),
17071781 getpid (),
@@ -1714,7 +1788,14 @@ static bool daemon_finish_active_run(Test_Daemon_Server *server) {
17141788 payload .cache_launcher_reason = (uint32_t )server -> current_telemetry .launcher_cache_reason ;
17151789 payload .cache_preflight_reason = (uint32_t )server -> current_telemetry .preflight_reason ;
17161790 (void )daemon_copy_string (payload .detail , sizeof (payload .detail ), server -> status_detail );
1717- ok = test_daemon_send_result (server -> current_client_fd , server -> current_request_id , & payload );
1791+ if (!have_attached_client ) {
1792+ ok = true;
1793+ } else if (!test_daemon_send_result (server -> current_client_fd , server -> current_request_id , & payload )) {
1794+ daemon_handle_client_disconnect (server );
1795+ ok = true;
1796+ } else {
1797+ ok = true;
1798+ }
17181799 }
17191800
17201801 if (server -> worker_result_path [0 ] != '\0' ) (void )unlink (server -> worker_result_path );
@@ -2017,7 +2098,8 @@ static bool daemon_handle_control(Test_Daemon_Server *server,
20172098 return daemon_send_status_result (fd ,
20182099 request_id ,
20192100 server ,
2020- test_daemon_state_name (state ));
2101+ server -> watch .active ? daemon_watch_status_summary (server )
2102+ : test_daemon_state_name (state ));
20212103
20222104 case TEST_DAEMON_CONTROL_STOP :
20232105 if (server -> shutting_down ) {
0 commit comments