roboflow/supervision · error · ValueError

Invalid characters in color hash

Error message

Invalid characters in color hash

What it means

Raised by _validate_color_hex when a hex color string contains characters outside [0-9a-fA-F] after stripping an optional leading '#'. All Color.from_hex_color() / palette parsing goes through this validator. It guards the subsequent length check (3/4/6/8 digits) and the int(hex, 16) parse from garbage input like 'GGG' or '#12G45F'.

Source

Thrown at src/supervision/draw/color.py:58

    "#46F0F0",
    "#F032E6",
    "#D2F53C",
    "#FABEBE",
    "#008080",
    "#E6BEFF",
    "#AA6E28",
    "#FFFAC8",
    "#800000",
    "#AAFFC3",
]

ROBOFLOW_COLOR_PALETTE = ["C28DFC", "A351FB", "8315F9", "6706CE", "5905B3", "4D049A"]


def _validate_color_hex(color_hex: str) -> None:
    color_hex = color_hex.lstrip("#")
    if not all(c in "0123456789abcdefABCDEF" for c in color_hex):
        raise ValueError("Invalid characters in color hash")
    if len(color_hex) not in (3, 4, 6, 8):
        raise ValueError("Invalid length of color hash")


@dataclass
class Color:
    """
    Represents a color in RGBA format.

    This class provides methods to work with colors, including creating colors from hex
    codes, converting colors to hex strings, RGB tuples, BGR tuples, RGBA tuples,
    and BGRA tuples.

    Attributes:
        r: Red channel value (0-255).
        g: Green channel value (0-255).
        b: Blue channel value (0-255).
        a: Alpha channel value (0-255). Default is 255 (fully opaque).

View on GitHub (pinned to 7f254d9784)

Solutions

  1. Normalize input before use: strip whitespace, allow only one leading '#', then validate with a regex like ^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$
  2. Convert named/CSS colors to hex first (e.g. matplotlib.colors.to_hex)
  3. Pre-validate UI color pickers to emit hex only

Example fix

# before
color = Color.from_hex_color('#ff00gg')  # 'g' is not a hex digit

# after
import re

if not re.fullmatch(r'#?[0-9a-fA-F]{3,4}|#?[0-9a-fA-F]{6}|#?[0-9a-fA-F]{8}', value):
    raise ValueError(f'Not a hex color: {value!r}')
color = Color.from_hex_color(value.lower())
Defensive patterns

Strategy: validation

Validate before calling

import re

HEX_RE = re.compile(r'^#?([0-9a-fA-F]{3}|[0-9a-fA-F]{4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})$')

def normalize_hex(value: str) -> str:
    value = value.strip()
    if not HEX_RE.fullmatch(value):
        raise ValueError(f'not a hex color: {value!r}')
    return value

color = Color.from_hex_color(normalize_hex(user_input))

Type guard

import re

HEX_COLOR_RE = re.compile(r'#?(?:[0-9a-fA-F]{3,4}|[0-9a-fA-F]{6}|[0-9a-fA-F]{8})')

def is_hex_color(value: object) -> bool:
    """True when value is a valid 3/4/6/8-digit hex color string."""
    return isinstance(value, str) and HEX_COLOR_RE.fullmatch(value) is not None

Try / catch

try:
    color = Color.from_hex_color(theme_color)
except ValueError as e:
    if 'color hash' in str(e):
        color = Color.WHITE  # safe default, log the bad value
    else:
        raise

Prevention

When it happens

Trigger: Calling Color.from_hex_color('xyz123'), passing a CSS rgb() string, including the '#' twice ('##ff0000' — the second '#' fails the character test), or typos like '#ff00gg'.

Common situations: User-supplied theme colors from a config/UI field without validation; colors copied with stray characters; abbreviations like 'rgb(255,0,0)' or named colors ('red') passed where hex is expected.

Understand the failure class

Related errors


AI-assisted analysis of roboflow/supervision@7f254d9784 (2026-08-15). Data as JSON: /api/errors/d9aa68629c677794. Report an issue: GitHub.