Summary
winapp init throws an unhandled System.InvalidOperationException with a Spectre.Console stack trace when stdin is not a TTY (CI, npm scripts, build pipelines, agents, Start-Process -RedirectStandardInput, anything that pipes input). The user is dropped to a raw .NET exception dump instead of either:
- Auto-detecting non-interactive mode and applying defaults, or
- Exiting cleanly with an actionable error like
error: this command requires --use-defaults in non-interactive shells.
Two prompts are affected (both blocking):
- "No known projects type were found. Initialize with winapp.yaml here? [y/n] (n):" — when no project is detected
- "appxmanifest.xml already exists. Overwrite? [y/n] (y):" — when a manifest is found
There are likely more ConfirmationPrompt / TextPrompt call sites with the same problem; init is just the most commonly hit.
Environment
- winapp CLI version:
0.3.2-prerelease.23
- OS: Windows 11
- Install path:
C:\Users\<user>\AppData\Local\Microsoft\WindowsApps\winapp.exe (MSIX wrapper)
- Shell: PowerShell 7, stdin redirected via
Start-Process -RedirectStandardOutput/Error
Repro steps
Scenario A — empty directory (no project detected)
$dir = "$env:TEMP\winapp-init-repro-a"
New-Item -ItemType Directory -Force $dir | Out-Null
Set-Location $dir
$out = "$env:TEMP\init-a-out.txt"; $err = "$env:TEMP\init-a-err.txt"
$p = Start-Process winapp -ArgumentList "init" -NoNewWindow -Wait -PassThru `
-RedirectStandardOutput $out -RedirectStandardError $err
"Exit: $($p.ExitCode)"
Get-Content $err
Scenario B — detected project with existing manifest
$dir = "$env:TEMP\winapp-init-repro-b"
New-Item -ItemType Directory -Force $dir | Out-Null
"" | Out-File "$dir\CMakeLists.txt"
"<?xml version='1.0'?><Package/>" | Out-File "$dir\appxmanifest.xml"
Set-Location $dir
$out = "$env:TEMP\init-b-out.txt"; $err = "$env:TEMP\init-b-err.txt"
$p = Start-Process winapp -ArgumentList "init" -NoNewWindow -Wait -PassThru `
-RedirectStandardOutput $out -RedirectStandardError $err
"Exit: $($p.ExitCode)"
Get-Content $err
Actual results
Both scenarios exit 1 with this on stderr:
Unhandled exception: System.InvalidOperationException: Failed to read input in non-interactive mode.
at Spectre.Console.DefaultInput.<ReadKeyAsync>d__4.MoveNext() + 0x168
--- End of stack trace from previous location ---
at Spectre.Console.AnsiConsoleExtensions.<ReadLine>d__11.MoveNext() + 0x6c
--- End of stack trace from previous location ---
at Spectre.Console.TextPrompt`1.<>c__DisplayClass63_0.<<ShowAsync>b__0>d.MoveNext() + 0x70
--- End of stack trace from previous location ---
at Spectre.Console.Internal.DefaultExclusivityMode.<RunAsync>d__3`1.MoveNext() + 0x168
--- End of stack trace from previous location ---
at Spectre.Console.TextPrompt`1.<ShowAsync>d__63.MoveNext() + 0x64
--- End of stack trace from previous location ---
at Spectre.Console.ConfirmationPrompt.<ShowAsync>d__39.MoveNext() + 0x68
--- End of stack trace from previous location ---
at WinApp.Cli.Commands.InitCommand.Handler.<HandleNoProjectsFoundAsync>d__9.MoveNext() + 0x5c
...
at WinApp.Cli.Commands.InitCommand.Handler.<InvokeAsync>d__6.MoveNext() + 0x160
at System.CommandLine.Invocation.InvocationPipeline.<InvokeAsync>d__0.MoveNext() + 0xd0
Expected results
One of:
- Auto-detect non-interactive mode via
Console.IsInputRedirected and behave as if --use-defaults had been passed (preferred — matches how dotnet, gh, and npm handle this).
- Fail fast with a clean message:
error: 'winapp init' is interactive and stdin is not a TTY.
Re-run with '--use-defaults' (and an explicit directory) to apply defaults non-interactively.
No stack trace leak.
Workaround (confirmed working)
winapp init . --use-defaults
# Exit: 0, no prompts, manifest + yaml created
Suggested fix
src/winapp-CLI/WinApp.Cli/Commands/InitCommand.cs and any other site that calls ConfirmationPrompt.ShowAsync / TextPrompt.ShowAsync:
private static bool IsInteractive() =>
!Console.IsInputRedirected && !Console.IsOutputRedirected;
// At each prompt site:
if (!useDefaults && !IsInteractive())
{
// Option A: silently apply the default
useDefaults = true;
// Option B: fail fast
logger.LogError("'winapp init' requires --use-defaults when stdin is redirected.");
return 1;
}
Recommend Option A (auto-promote to --use-defaults) for prompts that already have a sensible default in the prompt itself — that's the existing semantic and matches user intent when they piped/redirected. Reserve Option B for prompts where there's no safe default.
A broader fix would be wrapping the top-level invocation pipeline in a try/catch for InvalidOperationException from Spectre so the user never sees the raw .NET stack trace regardless of which prompt fires.
Impact
- CI/build pipelines, npm postinstall hooks, MCP/agent tooling, dotnet tool wrappers — any non-TTY caller that runs
winapp init without --use-defaults hits this.
- Stack trace is confusing for non-.NET users and makes the failure look like a CLI bug rather than a missing flag.
- This was hit during a manual bug-bash session against
samples/cpp-app/test-bundle/ while following the documented dotnet bundle scenario adapted to C++.
Related
May be worth auditing all ConfirmationPrompt/TextPrompt/SelectionPrompt call sites in WinApp.Cli for the same crash mode.
Summary
winapp initthrows an unhandledSystem.InvalidOperationExceptionwith a Spectre.Console stack trace when stdin is not a TTY (CI, npm scripts, build pipelines, agents,Start-Process -RedirectStandardInput, anything that pipes input). The user is dropped to a raw .NET exception dump instead of either:error: this command requires --use-defaults in non-interactive shells.Two prompts are affected (both blocking):
There are likely more
ConfirmationPrompt/TextPromptcall sites with the same problem;initis just the most commonly hit.Environment
0.3.2-prerelease.23C:\Users\<user>\AppData\Local\Microsoft\WindowsApps\winapp.exe(MSIX wrapper)Start-Process -RedirectStandardOutput/ErrorRepro steps
Scenario A — empty directory (no project detected)
Scenario B — detected project with existing manifest
Actual results
Both scenarios exit
1with this on stderr:Expected results
One of:
Console.IsInputRedirectedand behave as if--use-defaultshad been passed (preferred — matches howdotnet,gh, andnpmhandle this).Workaround (confirmed working)
Suggested fix
src/winapp-CLI/WinApp.Cli/Commands/InitCommand.csand any other site that callsConfirmationPrompt.ShowAsync/TextPrompt.ShowAsync:Recommend Option A (auto-promote to
--use-defaults) for prompts that already have a sensible default in the prompt itself — that's the existing semantic and matches user intent when they piped/redirected. Reserve Option B for prompts where there's no safe default.A broader fix would be wrapping the top-level invocation pipeline in a
try/catchforInvalidOperationExceptionfrom Spectre so the user never sees the raw .NET stack trace regardless of which prompt fires.Impact
winapp initwithout--use-defaultshits this.samples/cpp-app/test-bundle/while following the documented dotnet bundle scenario adapted to C++.Related
May be worth auditing all
ConfirmationPrompt/TextPrompt/SelectionPromptcall sites inWinApp.Clifor the same crash mode.