kovidgoyal/kitty · info · ErrExec

Execute command

Error message

Execute command

What it means

ErrExec is a sentinel error in tools/cmd/at/shell.go declared with the message 'Execute command'; it is returned by shell_main when the interactive shell loop should finish by executing a command. The message is a label for the control-flow signal ('we have a command, now execute it'), not a diagnostic: `kitten`'s @ shell uses readline to build a command line and uses this error to unwind so the caller can os.exec the resulting command.

Source

Thrown at tools/cmd/at/shell.go:28

	"path/filepath"
	"strings"
	"time"

	"github.com/kovidgoyal/kitty/tools/cli"
	"github.com/kovidgoyal/kitty/tools/cli/markup"
	"github.com/kovidgoyal/kitty/tools/tui/loop"
	"github.com/kovidgoyal/kitty/tools/tui/readline"
	"github.com/kovidgoyal/kitty/tools/utils"
	"github.com/kovidgoyal/kitty/tools/utils/shlex"
)

var _ = fmt.Print

var formatter *markup.Context

const prompt = "🐱 "

var ErrExec = errors.New("Execute command")

func shell_loop(rl *readline.Readline, kill_if_signaled bool) (int, error) {
	lp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors)
	if err != nil {
		return 1, err
	}
	rl.ChangeLoopAndResetText(lp)

	lp.OnInitialize = func() (string, error) {
		rl.Start()
		return "", nil
	}
	lp.OnFinalize = func() string { rl.End(); return "" }

	lp.OnResumeFromStop = func() error {
		rl.Start()
		return nil
	}

View on GitHub (pinned to 6d5d0c4406)

Solutions

  1. Recognize it as a sentinel: compare with errors.Is(err, ErrExec) and handle it by executing the pending command, not by reporting failure
  2. If writing a custom loop around shell_loop, mirror the upstream shell_main handling of this return value
  3. For debugging, read tools/cmd/at/shell.go's shell_main to see the intended control flow

Example fix

// before
if err != nil { return err } // bubbles 'Execute command' up as a failure

// after
if errors.Is(err, ErrExec) {
    // execute the command accumulated by the readline loop
}
if err != nil { return err }
Defensive patterns

Strategy: type-guard

Type guard

func isExecSignal(err error) bool {
    return errors.Is(err, atshell.ErrExec)
}

Try / catch

err := shell_main(...)
if errors.Is(err, atshell.ErrExec) {
    // execute the accumulated command; this is success, not failure
} else if err != nil {
    return err
}

Prevention

When it happens

Trigger: Running the interactive shell component of the `at` tool (shell_main/shell_loop): once the user submits a command, the loop returns ErrExec to signal the outer code to execute the collected command line.

Common situations: Almost never seen by end users; developers embedding or testing the at-shell loop who mishandle the sentinel as a real error; grepping error strings and being confused that the message looks like a user-facing failure.

Related errors


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