Status component ignores NO_COLOR when output is redirected to file
Opened this issue · 3 comments
Information
- OS: Windows and MacOS
- Version: 0.50.0
- Terminal: N/A (command is piped to a file)
Describe the bug
When the NO_COLOR environment variable is specified, the Status feature of Spectre.Console still writes ANSI escape sequences when piping the command to a file. While console output correctly honors NO_COLOR, redirected output (e.g., to a log file) still contains ANSI escape sequences. This appears to specifically affect the Status component's spinner and positioning escape sequences.
To Reproduce
- Set the NO_COLOR environment variable (e.g.,
export NO_COLOR=1in shell configuration) - Run a command that uses Spectre.Console's Status feature
- Pipe the output to a file (e.g.,
command > output.logorcommand >> output.log 2>&1) - Examine the output file and observe ANSI escape sequences are still present
Expected behavior
When NO_COLOR is set, no ANSI escape sequences should be written to the output, regardless of whether output is displayed in the terminal or redirected to a file.
Screenshots
N/A
Additional context
Example of problematic output in redirected file containing escape sequences:
[?25l
⣷ Updating Git Repositories...
[2A
⣯ Updating Git Repositories...
[2A
⣟ Updating Git Repositories...
[2A
⡿ Updating Git Repositories...
[WRN] Config Templates Repo: Deleting local git repo and retrying git operation due to error
[WRN] Config Templates Repo: Using explicit SHA1 for local repository: 123abc456def7890abcdef1234567890abcdef12
[INF] Config Templates Repo: Cloning...
[2A
⢿ Updating Git Repositories...
[2K[1A[2K[1A[2K[?25h
Example code using the Status feature:
internal class ConsoleMultiRepoUpdater(
IAnsiConsole console,
IReadOnlyCollection<IUpdateableRepo> repos
) : IMultiRepoUpdater
{
public async Task UpdateAllRepositories(bool hideConsoleOutput, CancellationToken token)
{
var options = new ParallelOptions { CancellationToken = token, MaxDegreeOfParallelism = 3 };
var task = Parallel.ForEachAsync(
repos,
options,
async (repo, innerToken) => await repo.Update(innerToken)
);
if (!hideConsoleOutput)
{
await console.Status().StartAsync("Updating Git Repositories...", _ => task);
}
else
{
await task;
}
}
}I've successfully reproduced this issue consistently on:
- Windows using both Git Bash and PowerShell terminals when piping to a file
- macOS (M2 Max MacBook) using iTerm terminal
Interestingly, when the same command is run from crontab, the output doesn't contain ANSI sequences. This suggests that Spectre.Console might be correctly detecting some console/redirection environment conditions but not others.
The issue specifically occurs when using IAnsiConsole.Status() method with the NO_COLOR environment variable set and when output is redirected to a file through normal shell redirection.
For additional details and reproduction steps from a user of a project that uses Spectre.Console, please see: recyclarr/recyclarr#462
How would it animate without ANSI escape characters?
The real bug here is that Spectre.Console seems to think that interactivity is supported, even though output has been redirected.
When no interactivity is supported, the Status widget, should output something like:
Updating Git Repositories...
[WRN] Config Templates Repo: Deleting local git repo and retrying git operation due to error
[WRN] Config Templates Repo: Using explicit SHA1 for local repository: 123abc456def7890abcdef1234567890abcdef12
[INF] Config Templates Repo: Cloning...
How would it animate without ANSI escape characters?
I'm not sure; I was hoping you could tell me. I'm not confident in my understanding of the underlying implementation. At the moment my understanding is that the ASCII escape characters are only necessary for coloring, and the Unicode ⣯ characters for the actual animation can still be output, or perhaps simplified to ascii characters for the animating bits. Your documentation even has an example of a simple scenario where the terminal can't display them (albeit for Progress, not status).
If my understanding is wrong, that's fine, happy to read some clarification on it. But hopefully the underlying issue is clear. I've had to manage this state myself by checking IsTerminal and deciding when to essentially skip the Status code (you can see this in my earlier example code).
I appreciate the quick reply.
@rcdailey You might use this RedirectionFriendlyConsole class to workaround your issue.
using System;
using System.IO;
using System.Text;
using Spectre.Console;
namespace SampleCode;
public static class RedirectionFriendlyConsole
{
public static IAnsiConsole Out { get; } = CreateRedirectionFriendlyConsole(Console.Out);
public static IAnsiConsole Error { get; } = CreateRedirectionFriendlyConsole(Console.Error);
private static IAnsiConsole CreateRedirectionFriendlyConsole(TextWriter textWriter)
{
var output = new RedirectionFriendlyAnsiConsoleOutput(new AnsiConsoleOutput(textWriter));
var settings = new AnsiConsoleSettings
{
Out = output,
Ansi = output.IsTerminal ? AnsiSupport.Detect : AnsiSupport.No,
};
return AnsiConsole.Create(settings);
}
private sealed class RedirectionFriendlyAnsiConsoleOutput(IAnsiConsoleOutput ansiConsoleOutput) : IAnsiConsoleOutput
{
public TextWriter Writer => ansiConsoleOutput.Writer;
public bool IsTerminal => ansiConsoleOutput.IsTerminal;
public int Width => IsTerminal ? ansiConsoleOutput.Width : 320;
public int Height => IsTerminal ? ansiConsoleOutput.Height : 240;
public void SetEncoding(Encoding encoding) => ansiConsoleOutput.SetEncoding(encoding);
}
}@patriksvensson I never really thought this through but maybe we have indeed a bug in ANSI detection. When redirected to a file, my instinct says Ansi should be false.