jesseduffield/lazygit · error

All gui.spinner.frames entries must have the same width.

Error message

All gui.spinner.frames entries must have the same width.

What it means

Validation error from validateSpinner (user_config_validation.go:108). All gui.spinner.frames entries must have the same display width (measured with utils.StringWidth, unicode-aware); mixed-width frames make the spinner jitter and break layout, so config load fails. The first frame's width is the reference.

Solutions

  1. Inspect each frame for stray spaces or hidden characters (e.g. with a hex view)
  2. Use frames from a single character set — all braille, all ASCII, or all emoji
  3. Pad narrower frames with spaces to match the widest frame's column width
  4. Validate widths yourself with a unicode-width aware function before saving

Example fix

# before
gui:
  spinner:
    frames: ['⠋', '🌧️']  # width 1 vs width 2

# after
gui:
  spinner:
    frames: ['⠋', '⠙', '⠹', '⠸']  # uniform width 1
Defensive patterns

Strategy: type-guard

Validate before calling

import "github.com/mattn/go-runewidth"

func uniformSpinnerWidth(frames []string) bool {
    if len(frames) == 0 {
        return false
    }
    w := runewidth.StringWidth(frames[0])
    for _, f := range frames[1:] {
        if runewidth.StringWidth(f) != w {
            return false
        }
    }
    return true
}

Type guard

func isUniformWidthFrameSet(frames []string) bool {
    return uniformSpinnerWidth(frames)
}

Prevention

When it happens

Trigger: Mixing frames of differing widths, including double-width CJK/emoji frames with narrow ASCII ones, or combining '⠋' with '-'. utils.StringWidth accounts for East Asian wide characters, so width is visual columns, not rune count.

Common situations: Copy-pasting a spinner from a font/terminal that renders differently; mixing emoji (often width 2) with braille frames (width 1); hand-crafted frames with a trailing space in only some entries.

Related errors


AI-assisted analysis of jesseduffield/lazygit@c477a2959b (2026-08-15). Data as JSON: /api/errors/507deae9cf2b3f86. Report an issue: GitHub.

Appendix: source

Thrown at pkg/config/user_config_validation.go:108

	// conflict or popping a stash), so they must always be present; otherwise
	// that code would focus a hidden panel.
	for _, required := range []string{"files", "branches", "commits"} {
		if !seen[required] {
			return fmt.Errorf("gui.sidePanels: '%s' must be included; it can't be hidden.", required)
		}
	}
	return nil
}

func validateSpinner(spinner SpinnerConfig) error {
	if len(spinner.Frames) == 0 {
		return errors.New("gui.spinner.frames must not be empty.")
	}
	firstWidth := utils.StringWidth(spinner.Frames[0])
	if lo.SomeBy(spinner.Frames, func(frame string) bool {
		return utils.StringWidth(frame) != firstWidth
	}) {
		return errors.New("All gui.spinner.frames entries must have the same width.")
	}
	return nil
}

func validateDiffRenderers(diffRenderers []DiffRendererConfig) error {
	for _, diffRenderer := range diffRenderers {
		switch diffRenderer.Type {
		case "stdinFilter", "":
			if diffRenderer.Command == "" {
				return errors.New("git.diffRenderers: 'command' must be specified for diff renderer type 'stdinFilter'.")
			}
			if len(diffRenderer.Args) > 0 {
				return errors.New("git.diffRenderers: 'args' cannot be used with diff renderer type 'stdinFilter'.")
			}
		case "extDiff":
			if len(diffRenderer.Args) > 0 {
				return errors.New("git.diffRenderers: 'args' cannot be used with diff renderer type 'extDiff'.")
			}

View on GitHub (pinned to c477a2959b)