From f71ecf1c731a6e658d607aca8b05dc379ecd3803 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 12:02:55 +0000 Subject: [PATCH 1/3] Initial plan From f13f3cfcc1f07e330ac022f6f20bcbed559a8054 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Wed, 25 Feb 2026 12:05:56 +0000 Subject: [PATCH 2/3] Add TTY detection for interactive prompts to prevent agent hangs - CertCommand: detect non-interactive stdin before prompting, fail with clear error directing users to --force flag - ProxyEngine: skip first-run cert trust prompt in non-interactive mode, default to trusting the certificate - Update --force option help text to note it's required for non-interactive use Co-authored-by: waldekmastykarz <11164679+waldekmastykarz@users.noreply.github.com> --- DevProxy/Commands/CertCommand.cs | 8 +++++++- DevProxy/Proxy/ProxyEngine.cs | 15 +++++++++++++-- 2 files changed, 20 insertions(+), 3 deletions(-) diff --git a/DevProxy/Commands/CertCommand.cs b/DevProxy/Commands/CertCommand.cs index e5d6bf41..50d7e827 100644 --- a/DevProxy/Commands/CertCommand.cs +++ b/DevProxy/Commands/CertCommand.cs @@ -15,7 +15,7 @@ sealed class CertCommand : Command private readonly ILoggerFactory _loggerFactory; private readonly Option _forceOption = new("--force", "-f") { - Description = "Don't prompt for confirmation when removing the certificate" + Description = "Don't prompt for confirmation when removing the certificate. Required for non-interactive use (CI, piped stdin, automation)." }; public CertCommand(ILogger logger, ILoggerFactory loggerFactory) : @@ -83,6 +83,12 @@ public void RemoveCert(ParseResult parseResult) var isForced = parseResult.GetValue(_forceOption); if (!isForced) { + if (Console.IsInputRedirected) + { + _logger.LogError("Confirmation required but running in non-interactive mode. Use --force to skip confirmation."); + return; + } + var isConfirmed = PromptConfirmation("Do you want to remove the root certificate", acceptByDefault: false); if (!isConfirmed) { diff --git a/DevProxy/Proxy/ProxyEngine.cs b/DevProxy/Proxy/ProxyEngine.cs index ca945033..977c4bc0 100755 --- a/DevProxy/Proxy/ProxyEngine.cs +++ b/DevProxy/Proxy/ProxyEngine.cs @@ -229,8 +229,19 @@ private void FirstRunSetup() Console.WriteLine(); Console.WriteLine("Dev Proxy uses a self-signed certificate to intercept and inspect HTTPS traffic."); - Console.Write("Update the certificate in your Keychain so that it's trusted by your browser? (Y/n): "); - var answer = Console.ReadLine()?.Trim(); + + string? answer; + if (Console.IsInputRedirected) + { + // Non-interactive mode, default to trusting the certificate + _logger.LogInformation("Non-interactive mode detected. Defaulting to trusting the certificate."); + answer = "y"; + } + else + { + Console.Write("Update the certificate in your Keychain so that it's trusted by your browser? (Y/n): "); + answer = Console.ReadLine()?.Trim(); + } if (string.Equals(answer, "n", StringComparison.OrdinalIgnoreCase)) { From 8c8d0b078ef1802a951d9340ab89651ea40f9cb9 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 26 Feb 2026 07:47:43 +0000 Subject: [PATCH 3/3] Address review feedback: add CI env var check and return non-zero exit code - CertCommand and ProxyEngine: check both Console.IsInputRedirected and CI env var for non-interactive detection, matching ExecuteAsync pattern - RemoveCert returns int: exit code 1 for non-interactive failure or exceptions, 0 for success Co-authored-by: waldekmastykarz <11164679+waldekmastykarz@users.noreply.github.com> --- DevProxy/Commands/CertCommand.cs | 11 +++++++---- DevProxy/Proxy/ProxyEngine.cs | 3 ++- 2 files changed, 9 insertions(+), 5 deletions(-) diff --git a/DevProxy/Commands/CertCommand.cs b/DevProxy/Commands/CertCommand.cs index 50d7e827..bff35ed6 100644 --- a/DevProxy/Commands/CertCommand.cs +++ b/DevProxy/Commands/CertCommand.cs @@ -74,7 +74,7 @@ private async Task EnsureCertAsync() _logger.LogTrace("EnsureCertAsync() finished"); } - public void RemoveCert(ParseResult parseResult) + public int RemoveCert(ParseResult parseResult) { _logger.LogTrace("RemoveCert() called"); @@ -83,16 +83,17 @@ public void RemoveCert(ParseResult parseResult) var isForced = parseResult.GetValue(_forceOption); if (!isForced) { - if (Console.IsInputRedirected) + if (Console.IsInputRedirected || + Environment.GetEnvironmentVariable("CI") is not null) { _logger.LogError("Confirmation required but running in non-interactive mode. Use --force to skip confirmation."); - return; + return 1; } var isConfirmed = PromptConfirmation("Do you want to remove the root certificate", acceptByDefault: false); if (!isConfirmed) { - return; + return 0; } } @@ -117,10 +118,12 @@ public void RemoveCert(ParseResult parseResult) } _logger.LogInformation("DONE"); + return 0; } catch (Exception ex) { _logger.LogError(ex, "Error removing certificate"); + return 1; } finally { diff --git a/DevProxy/Proxy/ProxyEngine.cs b/DevProxy/Proxy/ProxyEngine.cs index ef004d9a..0fa08448 100755 --- a/DevProxy/Proxy/ProxyEngine.cs +++ b/DevProxy/Proxy/ProxyEngine.cs @@ -233,7 +233,8 @@ private void FirstRunSetup() Console.WriteLine("Dev Proxy uses a self-signed certificate to intercept and inspect HTTPS traffic."); string? answer; - if (Console.IsInputRedirected) + if (Console.IsInputRedirected || + Environment.GetEnvironmentVariable("CI") is not null) { // Non-interactive mode, default to trusting the certificate _logger.LogInformation("Non-interactive mode detected. Defaulting to trusting the certificate.");