@@ -11,6 +11,8 @@ use git::refs::AI_AUTHORSHIP_REFSPEC;
1111use std:: io:: { IsTerminal , Write } ;
1212use std:: process:: Command ;
1313use utils:: debug_log;
14+ #[ cfg( unix) ]
15+ use std:: os:: unix:: process:: ExitStatusExt ;
1416
1517use crate :: commands:: checkpoint_agent:: agent_preset:: {
1618 AgentCheckpointFlags , AgentCheckpointPreset , ClaudePreset , CursorPreset ,
@@ -28,6 +30,8 @@ struct Cli {
2830}
2931
3032fn main ( ) {
33+ // Ensure SIGPIPE uses the default action (terminate), and do not inherit ignored SIGPIPE
34+ reset_sigpipe_to_default ( ) ;
3135 // Initialize global configuration early
3236 config:: Config :: init ( ) ;
3337 // Get the binary name that was called
@@ -406,14 +410,13 @@ fn handle_commit(args: &[String]) {
406410 let status = child. wait ( ) ;
407411 match status {
408412 Ok ( status) => {
409- let code = status. code ( ) . unwrap_or ( 1 ) ;
410413 // If commit succeeded, run post-commit
411- if code == 0 {
414+ if status . success ( ) {
412415 if let Err ( e) = git:: post_commit:: post_commit ( & repo, false ) {
413416 eprintln ! ( "Post-commit failed: {}" , e) ;
414417 }
415418 }
416- std :: process :: exit ( code ) ;
419+ exit_with_status ( status ) ;
417420 }
418421 Err ( e) => {
419422 eprintln ! ( "Failed to wait for git commit process: {}" , e) ;
@@ -503,36 +506,34 @@ fn handle_push(args: &[String]) {
503506 } )
504507 . unwrap_or_default ( ) ;
505508
506- // Helper to run a git command and optionally forward output, returning exit code
507- fn run_git_and_forward ( args : & [ String ] , quiet : bool ) -> i32 {
508- let output = Command :: new ( config:: Config :: get ( ) . git_cmd ( ) )
509- . args ( args)
510- . output ( ) ;
509+ // Helper to run a git command and optionally forward output, returning ExitStatus
510+ fn run_git_and_forward ( args : & [ String ] , quiet : bool ) -> std:: process:: ExitStatus {
511+ let output = Command :: new ( config:: Config :: get ( ) . git_cmd ( ) ) . args ( args) . output ( ) ;
511512 match output {
512513 Ok ( output) => {
513514 if !quiet {
514515 if !output. stdout . is_empty ( ) {
515- std:: io:: stdout ( ) . write_all ( & output. stdout ) . unwrap ( ) ;
516+ let _ = std:: io:: stdout ( ) . write_all ( & output. stdout ) ;
516517 }
517518 if !output. stderr . is_empty ( ) {
518- std:: io:: stderr ( ) . write_all ( & output. stderr ) . unwrap ( ) ;
519+ let _ = std:: io:: stderr ( ) . write_all ( & output. stderr ) ;
519520 }
520521 }
521- output. status . code ( ) . unwrap_or ( 1 )
522+ output. status
522523 }
523524 Err ( e) => {
524525 eprintln ! ( "Failed to execute git command: {}" , e) ;
525- 1
526+ std :: process :: exit ( 1 ) ;
526527 }
527528 }
528529 }
529530
530531 // 1) Run exactly what the user typed (no arg mutation)
531532 let mut user_push = vec ! [ "push" . to_string( ) ] ;
532533 user_push. extend_from_slice ( args) ;
533- let status_code = run_git_and_forward ( & user_push, false ) ;
534- if status_code != 0 {
535- std :: process :: exit ( status_code ) ;
534+ let status = run_git_and_forward ( & user_push, false ) ;
535+ if !status . success ( ) {
536+ exit_with_status ( status ) ;
536537 }
537538
538539 // 2) Push authorship refs to the appropriate remote
@@ -571,7 +572,7 @@ fn handle_push(args: &[String]) {
571572 debug_log ( & format ! ( "pushing authorship refs: {:?}" , & push_authorship) ) ;
572573 }
573574 let auth_status = run_git_and_forward ( & push_authorship, quiet_second_push) ;
574- std :: process :: exit ( auth_status) ;
575+ exit_with_status ( auth_status) ;
575576 } else {
576577 eprintln ! ( "No git remotes found." ) ;
577578 std:: process:: exit ( 1 ) ;
@@ -609,7 +610,7 @@ fn proxy_to_git(args: &[String]) {
609610 let status = child. wait ( ) ;
610611 match status {
611612 Ok ( status) => {
612- std :: process :: exit ( status. code ( ) . unwrap_or ( 1 ) ) ;
613+ exit_with_status ( status) ;
613614 }
614615 Err ( e) => {
615616 eprintln ! ( "Failed to wait for git process: {}" , e) ;
@@ -624,6 +625,30 @@ fn proxy_to_git(args: &[String]) {
624625 }
625626}
626627
628+ // Ensure SIGPIPE default action, even if inherited ignored from a parent shell
629+ fn reset_sigpipe_to_default ( ) {
630+ #[ cfg( unix) ]
631+ unsafe {
632+ libc:: signal ( libc:: SIGPIPE , libc:: SIG_DFL ) ;
633+ }
634+ }
635+
636+ // Exit mirroring the child's termination: same signal if signaled, else exit code
637+ fn exit_with_status ( status : std:: process:: ExitStatus ) -> ! {
638+ #[ cfg( unix) ]
639+ {
640+ if let Some ( sig) = status. signal ( ) {
641+ unsafe {
642+ libc:: signal ( sig, libc:: SIG_DFL ) ;
643+ libc:: raise ( sig) ;
644+ }
645+ // Should not return
646+ unreachable ! ( ) ;
647+ }
648+ }
649+ std:: process:: exit ( status. code ( ) . unwrap_or ( 1 ) ) ;
650+ }
651+
627652#[ allow( dead_code) ]
628653fn parse_file_with_line_range ( file_arg : & str ) -> ( String , Option < ( u32 , u32 ) > ) {
629654 if let Some ( colon_pos) = file_arg. rfind ( ':' ) {
0 commit comments