kovidgoyal/kitty · error

length not a multiple of 3

Error message

length not a multiple of 3

What it means

Raised by parse_sharp in tools/utils/style/wrapper.go when parsing a #-prefixed hex color string whose remainder (after stripping '#') has a length that is not divisible by 3. The parser splits the hex digits into three equal parts for R, G and B channels, so only balanced forms like #rgb, #rrggbb, #rrrgggbbb, #rrrrggggbbbb are accepted. Any other length (e.g. 4, 5, 7 hex digits) is rejected before numeric parsing.

Source

Thrown at tools/utils/style/wrapper.go:152

			}
			prefix = append(prefix, strconv.Itoa(number_base+num))
		} else {
			prefix = append(prefix, fmt.Sprintf("%d:5:%d", number_base+8, num))
		}
	} else {
		prefix = append(prefix, fmt.Sprintf("%d:2:%d:%d:%d", number_base+8, self.val.Red, self.val.Green, self.val.Blue))
	}
	return prefix, suffix
}

type color_value struct {
	is_set bool
	val    color_type
}

func parse_sharp(color string) (ans RGBA, err error) {
	if len(color)%3 != 0 {
		return RGBA{}, fmt.Errorf("length not a multiple of 3")
	}
	part_size := len(color) / 3
	r, g, b := color[:part_size], color[part_size:2*part_size], color[part_size*2:part_size*3]
	if !ans.parse_rgb_strings(r, g, b) {
		err = fmt.Errorf("invalid rgb numbers")
	}
	return
}

func parse_rgb(color string) (ans RGBA, err error) {
	colors := strings.Split(color, "/")
	if len(colors) != 3 {
		return RGBA{}, fmt.Errorf("length not a multiple of 3")
	}
	if ans.parse_rgb_strings(colors[0], colors[1], colors[2]) {
		return
	}
	err = fmt.Errorf("invalid rgb numbers")

View on GitHub (pinned to 6d5d0c4406)

Solutions

  1. Correct the hex color to a 3-divisible form: #rgb, #rrggbb (most common), or longer equal-part forms.
  2. If you intended RGBA with alpha, drop the alpha component — this parser supports only R/G/B equal-length parts.
  3. Validate user-supplied colors with a regex like ^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6})$ before passing them to ParseColor.
  4. Check for stray characters or comments appended to the color string before the parse.

Example fix

// before
col, err := style.ParseColor("#ff00") // length 4, not a multiple of 3

// after
col, err := style.ParseColor("#ff0000") // 6 hex digits, valid rrggbb
Defensive patterns

Strategy: validation

Validate before calling

var hexColorRe = regexp.MustCompile(`^#([0-9a-fA-F]{3}|[0-9a-fA-F]{6}|[0-9a-fA-F]{9}|[0-9a-fA-F]{12})$`)

func validHexColor(s string) bool { return hexColorRe.MatchString(strings.TrimSpace(s)) }

Type guard

func isHexColor(s string) bool {
	s = strings.TrimPrefix(strings.TrimSpace(strings.ToLower(s)), "#")
	return len(s)%3 == 0 && len(s) > 0
}

Prevention

When it happens

Trigger: Calling ParseColor (or a style helper like to_color / Set / parse_background) with a malformed hex color such as "#ff00" (4 digits), "#ff000" (5 digits) or "#fffffff" (7 digits). Note ParseColor lowercases and strips inline comments, but still requires the remaining hex portion after '#' to divide evenly by 3.

Common situations: Typos in color literals (missing or extra digit), copying 4-digit RGBA hex (#ff0f → 'ff0f' is 4 chars, invalid here since this parser has no alpha channel), or truncation of config values like "#ff00" instead of "#ff0000" in theme/settings files.

Related errors


AI-assisted analysis of kovidgoyal/kitty@6d5d0c4406 (2026-08-27). Data as JSON: /api/errors/8a4a13622d075abc. Report an issue: GitHub.