Skip to content

Comments

Route dotnet-ef diagnostic output to stderr, fix premature help exit code#37744

Merged
roji merged 5 commits intomainfrom
copilot/adhere-to-stdout-stderr-conventions
Feb 20, 2026
Merged

Route dotnet-ef diagnostic output to stderr, fix premature help exit code#37744
roji merged 5 commits intomainfrom
copilot/adhere-to-stdout-stderr-conventions

Conversation

Copy link
Contributor

Copilot AI commented Feb 19, 2026

dotnet ef writes diagnostic messages ("Build started...", "Build succeeded.") to stdout, breaking piping and redirection (e.g., dotnet ef migrations list | ... or dotnet ef migrations script > migrate.sql). Additionally, misplaced options like dotnet ef database -c CtxA update silently show help and exit 0.

Reporter: stderr for diagnostics, stdout for data

  • WriteError, WriteWarning, WriteInformation, WriteVerbosestderr
  • WriteDatastdout (unchanged)
  • When --prefix-output is active (PMC/MSBuild), all output stays on stdout for backward compatibility
# Before: stdout contains build noise
$ dotnet ef migrations list > migrations.txt  # file includes "Build started..."

# After: only migration data hits stdout
$ dotnet ef migrations list > migrations.txt  # clean output

AnsiConsole: add stderr writer

  • Added AnsiConsole.Error backed by Console.Error for colored stderr output, mirroring existing AnsiConsole.Out

ShouldHelp: check full args, not just command prefix

  • ShouldHelp previously used commands (args up to first - flag) to decide whether to show help — dotnet ef database -c CtxA update yielded commands = ["database"], triggering help with exit 0
  • Now checks args.Count (all remaining args) so help only triggers when the user genuinely typed just a top-level command with nothing else
Original prompt

This section details on the original issue you should resolve

<issue_title>Adhere to stdout/stderr conventions</issue_title>
<issue_description>

Problem

The problem is ef cli's unconventional stdout behavior which violates norms and expectations (coming from a *nix background, in particular). The downstream effects of this unconventional behavior are lack of interoperability with other tools and added friction when attempting to script the ef-cli.

Some example scenarios:

  1. Attempting to iterate over all migrations: for m in $(dotnet ef migrations list); do

    This command fails because the first two lines of output (on STDOUT) are Build started... and Build succeeded.. These lines are diagnostic output which should not be printed to STDOUT, but instead to STDERR. The only output on STDOUT from dotnet ef migrations list should be the actual list of migrations, such that the output can be piped or iterated over or consumed by other tooling.

  2. Attempting to generate the migration script: dotnet ef migrations script > migrate.sql

    This command likewise fails to produce the expected output because the first two lines of the output again are Build started... and Build succeeded.. I grant that this is a contrived example because, lo!, there exists the -o option! So some more background:

    A new user to this command might naively run: dotnet ef migrations script, and what do they see printed to STDOUT, but the desired SQL! And given that there is so much output, they would likely only see the last 50 or so lines of the sql script. Assuming that the command prints the SQL to STDOUT (because it does), they go about redirecting the output to a file, because that is the normal behavior on the CLI. Then this user might attempt to run the captured sql script, and encounter a sql syntax error because, unbeknownst to them, the first 2 lines of the file are not SQL, but instead diagnostic output from the dotnet build.

    But even aside from the first-time experience, there are still scenarios where it would be desirable to print the SQL script (and only the SQL script) to STDOUT. In particular, the generated script presently emits a BOM even though the file is UTF-8. (which is nonsensical, if common in .net tooling) Which means our enterprising developers who may use sql tools that choke on UTF-8 BOMs need to adjust the file before consuming it. A solution: dotnet ef migrations script | dos2unix > migrate.sql (or dotnet ef migrations script | strip-bom > migrate.sql. In this scenario, using the -o flag to capture the output is not sufficient.

  3. Invalid options cause the CLI to print "help/usage" output (yay!) but exit with a zero status code (boo). dotnet ef database -pMyProj -c CtxA update

    Eagle-eyes will notice that the -c/--context option is not a valid option to the database subcommand, but is instead only respected by the update sub-subcommand. Therefore the CLI "fails" by instead printing the CLI usage/help output. However, the CLI still exits with a zero (SUCCESS) status code. This scenario remained unnoticed on our CI server for weeks before anyone noticed that it was failing to fail correctly!!!

Expectation

  1. dotnet ef migrations list should print only the migration names to STDOUT; build output should go to STDERR
  2. dotnet ef migrations script should print only the migration SQL to STDOUT; build output should go to STDERR (and even better, should omit the BOM 😸 )
  3. Commands that fail should exit with a nonzero exit status!!! This includes commands that fail to run at all because they receive an unknown option or flag.
    1. (As a precautionary note: dotnet ef somecommand --help should still exit with a zero status code, because in that invocation, the user is explicitly requesting the help output. I've seen too many tools, when updated to exit nonzero when given unexpected arguments, suddenly start exiting nonzero whenever they print help/usage output. I hope that overcorrection doesn't happen here.)

Original thread: dotnet/sdk#7289 (comment)</issue_description>

Comments on the Issue (you are @copilot in this section)


💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.

Copilot AI and others added 2 commits February 19, 2026 13:36
…x ShouldHelp for exit codes

Co-authored-by: roji <1862641+roji@users.noreply.github.com>
Co-authored-by: roji <1862641+roji@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix ef cli stdout behavior to comply with conventions Route dotnet-ef diagnostic output to stderr, fix premature help exit code Feb 19, 2026
Copilot AI requested a review from roji February 19, 2026 13:41
@roji roji marked this pull request as ready for review February 19, 2026 19:30
@roji roji requested a review from a team as a code owner February 19, 2026 19:30
Copilot AI review requested due to automatic review settings February 19, 2026 19:30
@roji roji assigned AndriySvyryd and unassigned roji Feb 19, 2026
Copy link

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adjusts dotnet ef CLI output to better follow stdout/stderr conventions so that command output can be safely piped/redirected, and it fixes a help-triggering edge case that previously caused “help + exit 0” when additional arguments were present.

Changes:

  • Route diagnostic output (error/warn/info/verbose) to stderr, while keeping data output on stdout (with a stdout-only compatibility path when --prefix-output is enabled).
  • Add AnsiConsole.Error to support colored output to stderr (mirroring the existing AnsiConsole.Out).
  • Update ShouldHelp logic in the dotnet-ef wrapper to consider full remaining args, preventing premature help display for inputs like dotnet ef database -c CtxA update.

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated no comments.

File Description
src/ef/Reporter.cs Redirects diagnostics to stderr via a new WriteStdErr helper, preserving stdout-only behavior under --prefix-output.
src/ef/AnsiConsole.cs Adds AnsiConsole.Error backed by Console.Error for colored stderr output.
src/dotnet-ef/RootCommand.cs Fixes ShouldHelp to use the full remaining argument list so help only triggers for true “top-level only” invocations.

Copy link
Member

@AndriySvyryd AndriySvyryd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@roji How did you test this?

@roji
Copy link
Member

roji commented Feb 19, 2026

How did you test this?

My idea was to manually test it after the next preview gets released, but if you want to test now and have a way of doing it, let me know.

@AndriySvyryd
Copy link
Member

My idea was to manually test it after the next preview gets released, but if you want to test now and have a way of doing it, let me know.

It's pretty easy to install the local build, something like this:

...\EFCore\.dotnet\dotnet tool update dotnet-ef --version 11.0.0-dev --add-source ...\EFCore\artifacts\packages\Debug\Shipping --global --no-http-cache

@roji
Copy link
Member

roji commented Feb 20, 2026

Thanks @AndriySvyryd, I managed to manually test thanks to your instructions.

The fix was good - reporter messages such as "Build starting..." and "Build succeeded." indeed started appearing on stderr instead of stdout. However, EF logging messages still appears on stdout, which is not the right behavior; for example, when executing dotnet ef migrations script, I got the "Sensitive data logging is enabled" warning in stdout, along with the actual SQL.

I pushed an additional commit which redirects Console.Out to Err entirely, so that logging gets written there; Console.Out is still captured and used by the Reporter so that e.g. the SQL for the migration does end up in stdout and not err.

@roji roji requested a review from AndriySvyryd February 20, 2026 07:43
@AndriySvyryd
Copy link
Member

Did you verify the error code when help is shown due to an invalid argument?

@roji
Copy link
Member

roji commented Feb 20, 2026

Yes, I did.

  • Executing dotnet ef database -c CtxA update previously showed the usage message and exited with code 0 (success).
  • After this PR, it outputs Unrecognized option '-c' and exist with code 1 (failure).

@roji roji merged commit a00f52a into main Feb 20, 2026
10 checks passed
@roji roji deleted the copilot/adhere-to-stdout-stderr-conventions branch February 20, 2026 09:15
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Command-line tools should adhere to stdout/stderr conventions

3 participants