@@ -4,13 +4,20 @@ use moon_common::is_ci;
44use moon_common:: path:: { WorkspaceRelativePathBuf , standardize_separators} ;
55use moon_env_var:: GlobalEnvBag ;
66use moon_vcs:: { BoxedVcs , ChangedFiles , ChangedStatus } ;
7+ #[ cfg( unix) ]
8+ use nix:: poll:: { PollFd , PollFlags , PollTimeout , poll} ;
79use rustc_hash:: FxHashSet ;
810use serde:: { Deserialize , Serialize } ;
911use starbase_styles:: color;
1012use starbase_utils:: json;
13+ #[ cfg( unix) ]
14+ use std:: os:: fd:: AsFd ;
1115use std:: io:: { IsTerminal , Read , stdin} ;
1216use tracing:: { debug, warn} ;
1317
18+ #[ cfg( unix) ]
19+ const STDIN_READY_TIMEOUT_MS : u8 = 100 ;
20+
1421#[ derive( Clone , Debug , Default , Deserialize , Serialize ) ]
1522#[ serde( rename_all = "camelCase" ) ]
1623pub struct QueryChangedFilesOptions {
@@ -225,7 +232,7 @@ pub async fn query_changed_files_for_affected(
225232 let mut options = QueryChangedFilesOptions {
226233 default_branch : ci,
227234 local : !ci,
228- stdin : true ,
235+ stdin : should_read_from_stdin ( ) ? ,
229236 ..Default :: default ( )
230237 } ;
231238
@@ -237,3 +244,35 @@ pub async fn query_changed_files_for_affected(
237244 . await
238245 . map ( |result| result. files )
239246}
247+
248+ fn should_read_from_stdin ( ) -> miette:: Result < bool > {
249+ if stdin ( ) . is_terminal ( ) {
250+ return Ok ( false ) ;
251+ }
252+
253+ // Non-TTY stdin can be an open CI handle with no data, so only read when ready.
254+ is_stdin_readable ( )
255+ }
256+
257+ #[ cfg( unix) ]
258+ fn is_stdin_readable ( ) -> miette:: Result < bool > {
259+ let stdin = stdin ( ) ;
260+ let mut poll_fds = [ PollFd :: new ( stdin. as_fd ( ) , PollFlags :: POLLIN ) ] ;
261+ let ready = poll (
262+ & mut poll_fds,
263+ PollTimeout :: from ( STDIN_READY_TIMEOUT_MS ) ,
264+ )
265+ . into_diagnostic ( ) ?;
266+
267+ let readable_events = PollFlags :: POLLIN | PollFlags :: POLLHUP | PollFlags :: POLLERR ;
268+
269+ Ok ( ready > 0
270+ && poll_fds[ 0 ]
271+ . revents ( )
272+ . is_some_and ( |events| events. intersects ( readable_events) ) )
273+ }
274+
275+ #[ cfg( not( unix) ) ]
276+ fn is_stdin_readable ( ) -> miette:: Result < bool > {
277+ Ok ( true )
278+ }
0 commit comments