@@ -376,13 +376,23 @@ ActionResult Fail(string msg)
376376 } ;
377377
378378 using Process process = Process . Start ( startInfo ) ;
379+
380+ // Start draining stdout/stderr BEFORE WaitForExit. The default
381+ // OS pipe buffer is small (4 KiB-ish on Windows), so if the
382+ // child writes more than the buffer holds while we're still
383+ // sitting on WaitForExit, the child blocks on a full pipe and
384+ // we kill it for "timing out" — classic .NET deadlock.
385+ // Running both reads concurrently (as tasks) means the buffer
386+ // is drained as the child fills it.
387+ System . Threading . Tasks . Task < string > stdoutTask = process . StandardOutput . ReadToEndAsync ( ) ;
388+ System . Threading . Tasks . Task < string > stderrTask = process . StandardError . ReadToEndAsync ( ) ;
379389 if ( ! process . WaitForExit ( 60_000 ) )
380390 {
381391 try { process . Kill ( ) ; } catch { /* already gone */ }
382392 return Fail ( "Agent tunnel enrollment timed out after 60 seconds." ) ;
383393 }
384- string stdout = process . StandardOutput . ReadToEnd ( ) ;
385- string stderr = process . StandardError . ReadToEnd ( ) ;
394+ string stdout = stdoutTask . GetAwaiter ( ) . GetResult ( ) ;
395+ string stderr = stderrTask . GetAwaiter ( ) . GetResult ( ) ;
386396
387397 if ( ! string . IsNullOrEmpty ( stdout ) ) session . Log ( $ "enrollment stdout: { Redact ( stdout ) } ") ;
388398 if ( ! string . IsNullOrEmpty ( stderr ) ) session . Log ( $ "enrollment stderr: { Redact ( stderr ) } ") ;
@@ -519,6 +529,15 @@ ActionResult Fail(string title, string detail, string nextStep)
519529 } ;
520530
521531 using Process process = Process . Start ( startInfo ) ;
532+
533+ // Drain stdout/stderr concurrently BEFORE WaitForExit so a
534+ // chatty child (verify-tunnel can emit a large diagnostic
535+ // payload on the failure path) cannot fill its pipe buffer
536+ // and deadlock waiting for us to read. See the matching
537+ // pattern in `EnrollAgentTunnel`.
538+ System . Threading . Tasks . Task < string > stdoutTask = process . StandardOutput . ReadToEndAsync ( ) ;
539+ System . Threading . Tasks . Task < string > stderrTask = process . StandardError . ReadToEndAsync ( ) ;
540+
522541 // Hard wall-clock cap a few seconds beyond the agent's own --timeout so a
523542 // misbehaving process can't hang the installer.
524543 if ( ! process . WaitForExit ( ( VerifyTimeoutSeconds + 5 ) * 1000 ) )
@@ -530,8 +549,8 @@ ActionResult Fail(string title, string detail, string nextStep)
530549 "Re-run the installer. If the failure repeats, network path likely drops UDP mid-flow; check Windows Firewall, NAT, and EDR network inspection." ) ;
531550 }
532551
533- string stdout = process . StandardOutput . ReadToEnd ( ) ;
534- string stderr = process . StandardError . ReadToEnd ( ) ;
552+ string stdout = stdoutTask . GetAwaiter ( ) . GetResult ( ) ;
553+ string stderr = stderrTask . GetAwaiter ( ) . GetResult ( ) ;
535554
536555 if ( ! string . IsNullOrEmpty ( stdout ) ) session . Log ( $ "verify-tunnel stdout: { stdout } ") ;
537556 if ( ! string . IsNullOrEmpty ( stderr ) ) session . Log ( $ "verify-tunnel stderr: { stderr } ") ;
0 commit comments