spectreconsole/spectre.console · error · InvalidOperationException

A foreground color has already been set.

Error message

A foreground color has already been set.

What it means

In the foreground position, a color was already assigned (effectiveForeground != null) and a second foreground color appears. Foreground can only be set once per tag; additional colors must go in the background slot after `on`.

Source

Thrown at src/Spectre.Console.Ansi/AnsiMarkupTagParser.cs:10

namespace Spectre.Console;

internal static class AnsiMarkupTagParser
{
    public static (Style Style, Link? Link) Parse(string text)
    {
        var result = Parse(text, out var error);
        if (error != null)
        {
            throw new InvalidOperationException(error);
        }

        return result ?? throw new InvalidOperationException("Could not parse style.");
    }

    public static bool TryParse(string text, [NotNullWhen(true)] out (Style Style, Link?)? result)
    {
        result = Parse(text, out _);
        return result != null;
    }

    private static (Style Style, Link? Link)? Parse(string text, out string? error)
    {
        var effectiveDecoration = (Decoration?)null;
        var effectiveForeground = (Color?)null;
        var effectiveBackground = (Color?)null;
        var effectiveLink = (string?)null;

View on GitHub (pinned to 0acc92fada)

Solutions

  1. Separate foreground and background with `on`: `[red on green]`
  2. Remove the duplicate foreground color
  3. Use `default` to explicitly skip a slot if needed

Example fix

// before
AnsiMarkup.Parse("[red green]text[/]");

// after
AnsiMarkup.Parse("[red on green]text[/]");
Defensive patterns

Strategy: validation

Validate before calling

// Ensure at most one foreground color token precedes `on`:
public static int ForegroundColorCount(string tag)
{
    var onIdx = tag.IndexOf(" on ", StringComparison.OrdinalIgnoreCase);
    var fg = onIdx < 0 ? tag : tag[..onIdx];
    return fg.Split(' ', StringSplitOptions.RemoveEmptyEntries)
             .Count(p => ColorTable.GetColor(p) != null || Regex.IsMatch(p, "^#[0-9A-Fa-f]{6}$"));
}

Try / catch

try { AnsiMarkup.Parse(markup); }
catch (InvalidOperationException ex) when (ex.Message == "A foreground color has already been set.")
{ /* move the second color to the background slot with `on` */ }

Prevention

When it happens

Trigger: Two colors before `on`, e.g. `[red green]`, `[1 2]`, or `[red #00ff00]`.

Common situations: Intending one color as background but forgetting the `on` separator; merging styles by concatenation that stacks two foregrounds.

Related errors


AI-assisted analysis of spectreconsole/spectre.console@0acc92fada (2026-08-13). Data as JSON: /api/errors/0503456bd78bc818. Report an issue: GitHub.