kovidgoyal/kitty · info · ErrCanceledByUser

Canceled by user

Error message

Canceled by user

What it means

ErrCanceledByUser is a sentinel error in the unicode_input kitten's Go handler returned from on_key_event when the user presses Escape or Ctrl+C, and from main when the session aborts that way. It is not a failure — it is the kitten's normal cancellation signal, and callers treat it as a clean exit (typically mapping it to exit code 1 or a no-op) rather than an error to report.

Source

Thrown at kittens/unicode_input/main.go:491

			self.err = err
			self.lp.Quit(1)
			return
		}
	}
}

func (self *handler) next_mode(delta int) {
	for num, md := range all_modes {
		if md.mode == self.mode {
			idx := (num + delta + len(all_modes)) % len(all_modes)
			md = all_modes[idx]
			self.switch_mode(md.mode)
			break
		}
	}
}

var ErrCanceledByUser = errors.New("Canceled by user")

func (self *handler) on_key_event(event *loop.KeyEvent) (err error) {
	if event.MatchesPressOrRepeat("esc") || event.MatchesPressOrRepeat("ctrl+c") {
		return ErrCanceledByUser
	}
	if event.MatchesPressOrRepeat("f1") || event.MatchesPressOrRepeat("ctrl+1") {
		event.Handled = true
		self.switch_mode(HEX)
	} else if event.MatchesPressOrRepeat("f2") || event.MatchesPressOrRepeat("ctrl+2") {
		event.Handled = true
		self.switch_mode(NAME)
	} else if event.MatchesPressOrRepeat("f3") || event.MatchesPressOrRepeat("ctrl+3") {
		event.Handled = true
		self.switch_mode(EMOTICONS)
	} else if event.MatchesPressOrRepeat("f4") || event.MatchesPressOrRepeat("ctrl+4") {
		event.Handled = true
		self.switch_mode(FAVORITES)
	} else if event.MatchesPressOrRepeat("ctrl+tab") || event.MatchesPressOrRepeat("ctrl+]") {

View on GitHub (pinned to 6d5d0c4406)

Solutions

  1. Treat ErrCanceledByUser as a normal cancellation: check `errors.Is(err, ErrCanceledByUser)` and exit quietly (e.g. exit code 1) rather than printing an error
  2. If it appears unexpectedly, check what's sending esc/ctrl+c to the kitten (key bindings, automated input)
  3. For scripted use, avoid launching the interactive kitten without a TTY or provide a codepoint via arguments instead

Example fix

// before
if err := main(); err != nil { fmt.Fprintln(os.Stderr, err) } // prints 'Canceled by user'

// after
if err := main(); err != nil {
    if !errors.Is(err, ErrCanceledByUser) {
        fmt.Fprintln(os.Stderr, err)
    }
    os.Exit(1)
}
Defensive patterns

Strategy: try-catch

Type guard

func isUserCancel(err error) bool {
    return errors.Is(err, unicode_input.ErrCanceledByUser)
}

Try / catch

if err := run(); err != nil {
    if errors.Is(err, unicode_input.ErrCanceledByUser) {
        return nil // clean cancel, no message
    }
    return err
}

Prevention

When it happens

Trigger: Opening the unicode input/emoji kitten and pressing esc or ctrl+c; on_key_event matches those keys and returns ErrCanceledByUser immediately, terminating the chooser without inserting a character.

Common situations: Scripts or wrappers invoking `kitten unicode_input` non-interactively and receiving this error because input closed/escaped; programmatically driving the kitten and interpreting the cancellation sentinel as a crash; key remappings that send esc more easily than intended.

Related errors


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