@@ -6,6 +6,28 @@ use cortex_common::CliConfigOverrides;
66use cortex_engine:: { config:: find_cortex_home, create_default_client} ;
77use std:: io:: { self , BufRead , Write } ;
88
9+ // ============================================================================
10+ // Safe Print Utilities (Issue #1989 - Handle broken pipe gracefully)
11+ // ============================================================================
12+
13+ /// Safely prints to stdout, ignoring broken pipe errors.
14+ /// This prevents crashes when output is piped to commands like `head` that close early.
15+ macro_rules! safe_println {
16+ ( ) => {
17+ let _ = writeln!( std:: io:: stdout( ) ) ;
18+ } ;
19+ ( $( $arg: tt) * ) => {
20+ let _ = writeln!( std:: io:: stdout( ) , $( $arg) * ) ;
21+ } ;
22+ }
23+
24+ /// Safely prints to stdout without newline, ignoring broken pipe errors.
25+ macro_rules! safe_print {
26+ ( $( $arg: tt) * ) => {
27+ let _ = write!( std:: io:: stdout( ) , $( $arg) * ) ;
28+ } ;
29+ }
30+
931// ============================================================================
1032// Input Validation Constants and Utilities
1133// ============================================================================
@@ -1397,14 +1419,15 @@ async fn run_debug(args: DebugArgs) -> Result<()> {
13971419
13981420 let mut errors: Vec < String > = Vec :: new ( ) ;
13991421
1422+ // Use safe_println! to avoid SIGPIPE crashes when output is piped (Issue #1989)
14001423 if !json {
1401- println ! ( "Debugging MCP Server: {name}" ) ;
1402- println ! ( "{}" , "=" . repeat( 50 ) ) ;
1403- println ! ( "Checked at: {} (fresh)" , check_timestamp) ;
1404- println ! ( ) ;
1405- println ! ( "Configuration:" ) ;
1406- println ! ( " Enabled: {enabled}" ) ;
1407- println ! ( " Transport: {transport_type}" ) ;
1424+ safe_println ! ( "Debugging MCP Server: {name}" ) ;
1425+ safe_println ! ( "{}" , "=" . repeat( 50 ) ) ;
1426+ safe_println ! ( "Checked at: {} (fresh)" , check_timestamp) ;
1427+ safe_println ! ( ) ;
1428+ safe_println ! ( "Configuration:" ) ;
1429+ safe_println ! ( " Enabled: {enabled}" ) ;
1430+ safe_println ! ( " Transport: {transport_type}" ) ;
14081431 }
14091432
14101433 // Transport-specific info
@@ -1429,9 +1452,9 @@ async fn run_debug(args: DebugArgs) -> Result<()> {
14291452 debug_result[ "args" ] = serde_json:: json!( args) ;
14301453
14311454 if !json {
1432- println ! ( " Command: {cmd}" ) ;
1455+ safe_println ! ( " Command: {cmd}" ) ;
14331456 if !args. is_empty ( ) {
1434- println ! ( " Args: {args}" ) ;
1457+ safe_println ! ( " Args: {args}" ) ;
14351458 }
14361459 }
14371460
@@ -1445,20 +1468,20 @@ async fn run_debug(args: DebugArgs) -> Result<()> {
14451468 debug_result[ "command_exists" ] = serde_json:: json!( cmd_exists) ;
14461469
14471470 if !json {
1448- println ! ( ) ;
1449- println ! ( "Command Check:" ) ;
1471+ safe_println ! ( ) ;
1472+ safe_println ! ( "Command Check:" ) ;
14501473 if cmd_exists {
1451- println ! ( " ✓ Command '{cmd}' found in PATH" ) ;
1474+ safe_println ! ( " ✓ Command '{cmd}' found in PATH" ) ;
14521475 } else {
1453- println ! ( " ✗ Command '{cmd}' not found in PATH" ) ;
1476+ safe_println ! ( " ✗ Command '{cmd}' not found in PATH" ) ;
14541477 errors. push ( format ! ( "Command '{}' not found in PATH" , cmd) ) ;
14551478 }
14561479 }
14571480
14581481 // Try to connect and get capabilities
14591482 if !json {
1460- println ! ( ) ;
1461- println ! ( "Connection Test:" ) ;
1483+ safe_println ! ( ) ;
1484+ safe_println ! ( "Connection Test:" ) ;
14621485 }
14631486
14641487 match test_stdio_connection ( & name, cmd, & args, timeout) . await {
@@ -1472,18 +1495,18 @@ async fn run_debug(args: DebugArgs) -> Result<()> {
14721495 debug_result[ "prompts" ] = info. prompts . clone ( ) ;
14731496
14741497 if !json {
1475- println ! ( " ✓ Connected successfully" ) ;
1476- println ! ( ) ;
1477- println ! ( "Server Capabilities:" ) ;
1498+ safe_println ! ( " ✓ Connected successfully" ) ;
1499+ safe_println ! ( ) ;
1500+ safe_println ! ( "Server Capabilities:" ) ;
14781501 if let Some ( caps) = info. capabilities . as_object ( ) {
14791502 for ( key, value) in caps {
1480- println ! ( " • {key}: {value}" ) ;
1503+ safe_println ! ( " • {key}: {value}" ) ;
14811504 }
14821505 } else {
1483- println ! ( " (none reported)" ) ;
1506+ safe_println ! ( " (none reported)" ) ;
14841507 }
1485- println ! ( ) ;
1486- println ! (
1508+ safe_println ! ( ) ;
1509+ safe_println ! (
14871510 "Available Tools: {}" ,
14881511 info. tools. as_array( ) . map( |a| a. len( ) ) . unwrap_or( 0 )
14891512 ) ;
@@ -1499,18 +1522,18 @@ async fn run_debug(args: DebugArgs) -> Result<()> {
14991522 } else {
15001523 desc. to_string ( )
15011524 } ;
1502- println ! ( " • {name}: {desc_short}" ) ;
1525+ safe_println ! ( " • {name}: {desc_short}" ) ;
15031526 }
15041527 }
15051528 if tools. len ( ) > 10 {
1506- println ! ( " ... and {} more" , tools. len( ) - 10 ) ;
1529+ safe_println ! ( " ... and {} more" , tools. len( ) - 10 ) ;
15071530 }
15081531 }
1509- println ! ( ) ;
1532+ safe_println ! ( ) ;
15101533 let resources_count =
15111534 info. resources . as_array ( ) . map ( |a| a. len ( ) ) . unwrap_or ( 0 ) ;
15121535 let prompts_count = info. prompts . as_array ( ) . map ( |a| a. len ( ) ) . unwrap_or ( 0 ) ;
1513- println ! (
1536+ safe_println ! (
15141537 "Available Resources: {}{}" ,
15151538 resources_count,
15161539 if resources_count == 0 {
@@ -1519,7 +1542,7 @@ async fn run_debug(args: DebugArgs) -> Result<()> {
15191542 ""
15201543 }
15211544 ) ;
1522- println ! (
1545+ safe_println ! (
15231546 "Available Prompts: {}{}" ,
15241547 prompts_count,
15251548 if prompts_count == 0 {
@@ -1539,7 +1562,7 @@ async fn run_debug(args: DebugArgs) -> Result<()> {
15391562 errors. push ( error_msg) ;
15401563
15411564 if !json {
1542- println ! ( " ✗ Connection failed: {e}" ) ;
1565+ safe_println ! ( " ✗ Connection failed: {e}" ) ;
15431566 }
15441567 }
15451568 }
@@ -1550,13 +1573,13 @@ async fn run_debug(args: DebugArgs) -> Result<()> {
15501573 debug_result[ "url" ] = serde_json:: json!( url) ;
15511574
15521575 if !json {
1553- println ! ( " URL: {url}" ) ;
1576+ safe_println ! ( " URL: {url}" ) ;
15541577 }
15551578
15561579 // Test HTTP connection
15571580 if !json {
1558- println ! ( ) ;
1559- println ! ( "Connection Test:" ) ;
1581+ safe_println ! ( ) ;
1582+ safe_println ! ( "Connection Test:" ) ;
15601583 }
15611584
15621585 match test_http_connection ( & name, url, timeout) . await {
@@ -1570,18 +1593,18 @@ async fn run_debug(args: DebugArgs) -> Result<()> {
15701593 debug_result[ "prompts" ] = info. prompts . clone ( ) ;
15711594
15721595 if !json {
1573- println ! ( " ✓ Connected successfully" ) ;
1574- println ! ( ) ;
1575- println ! ( "Server Capabilities:" ) ;
1596+ safe_println ! ( " ✓ Connected successfully" ) ;
1597+ safe_println ! ( ) ;
1598+ safe_println ! ( "Server Capabilities:" ) ;
15761599 if let Some ( caps) = info. capabilities . as_object ( ) {
15771600 for ( key, value) in caps {
1578- println ! ( " • {key}: {value}" ) ;
1601+ safe_println ! ( " • {key}: {value}" ) ;
15791602 }
15801603 } else {
1581- println ! ( " (none reported)" ) ;
1604+ safe_println ! ( " (none reported)" ) ;
15821605 }
1583- println ! ( ) ;
1584- println ! (
1606+ safe_println ! ( ) ;
1607+ safe_println ! (
15851608 "Available Tools: {}" ,
15861609 info. tools. as_array( ) . map( |a| a. len( ) ) . unwrap_or( 0 )
15871610 ) ;
@@ -1597,18 +1620,18 @@ async fn run_debug(args: DebugArgs) -> Result<()> {
15971620 } else {
15981621 desc. to_string ( )
15991622 } ;
1600- println ! ( " • {name}: {desc_short}" ) ;
1623+ safe_println ! ( " • {name}: {desc_short}" ) ;
16011624 }
16021625 }
16031626 if tools. len ( ) > 10 {
1604- println ! ( " ... and {} more" , tools. len( ) - 10 ) ;
1627+ safe_println ! ( " ... and {} more" , tools. len( ) - 10 ) ;
16051628 }
16061629 }
1607- println ! ( ) ;
1630+ safe_println ! ( ) ;
16081631 let resources_count =
16091632 info. resources . as_array ( ) . map ( |a| a. len ( ) ) . unwrap_or ( 0 ) ;
16101633 let prompts_count = info. prompts . as_array ( ) . map ( |a| a. len ( ) ) . unwrap_or ( 0 ) ;
1611- println ! (
1634+ safe_println ! (
16121635 "Available Resources: {}{}" ,
16131636 resources_count,
16141637 if resources_count == 0 {
@@ -1617,7 +1640,7 @@ async fn run_debug(args: DebugArgs) -> Result<()> {
16171640 ""
16181641 }
16191642 ) ;
1620- println ! (
1643+ safe_println ! (
16211644 "Available Prompts: {}{}" ,
16221645 prompts_count,
16231646 if prompts_count == 0 {
@@ -1637,16 +1660,16 @@ async fn run_debug(args: DebugArgs) -> Result<()> {
16371660 errors. push ( error_msg) ;
16381661
16391662 if !json {
1640- println ! ( " ✗ Connection failed: {e}" ) ;
1663+ safe_println ! ( " ✗ Connection failed: {e}" ) ;
16411664 }
16421665 }
16431666 }
16441667
16451668 // Check OAuth status
16461669 if test_auth {
16471670 if !json {
1648- println ! ( ) ;
1649- println ! ( "OAuth Status:" ) ;
1671+ safe_println ! ( ) ;
1672+ safe_println ! ( "OAuth Status:" ) ;
16501673 }
16511674
16521675 let auth_status = get_auth_status_for_display ( & name, url)
@@ -1655,18 +1678,18 @@ async fn run_debug(args: DebugArgs) -> Result<()> {
16551678 debug_result[ "auth_status" ] = serde_json:: json!( auth_status) ;
16561679
16571680 if !json {
1658- println ! ( " {auth_status}" ) ;
1681+ safe_println ! ( " {auth_status}" ) ;
16591682
16601683 if auth_status == "Not Authenticated" {
1661- println ! ( "\n Use 'cortex mcp auth {name}' to authenticate." ) ;
1684+ safe_println ! ( "\n Use 'cortex mcp auth {name}' to authenticate." ) ;
16621685 }
16631686 }
16641687 }
16651688 }
16661689 _ => {
16671690 errors. push ( format ! ( "Unknown transport type: {}" , transport_type) ) ;
16681691 if !json {
1669- println ! ( " ✗ Unknown transport type: {transport_type}" ) ;
1692+ safe_println ! ( " ✗ Unknown transport type: {transport_type}" ) ;
16701693 }
16711694 }
16721695 }
@@ -1678,12 +1701,12 @@ async fn run_debug(args: DebugArgs) -> Result<()> {
16781701
16791702 if json {
16801703 let output = serde_json:: to_string_pretty ( & debug_result) ?;
1681- println ! ( "{output}" ) ;
1704+ safe_println ! ( "{output}" ) ;
16821705 } else if !errors. is_empty ( ) {
1683- println ! ( ) ;
1684- println ! ( "Errors:" ) ;
1706+ safe_println ! ( ) ;
1707+ safe_println ! ( "Errors:" ) ;
16851708 for err in & errors {
1686- println ! ( " ✗ {err}" ) ;
1709+ safe_println ! ( " ✗ {err}" ) ;
16871710 }
16881711 }
16891712
0 commit comments