{"record":{"id":"cc99f89ebbf31c43","repo":"kovidgoyal/kitty","slug":"execute-command","errorCode":null,"errorMessage":"Execute command","messagePattern":"Execute command","errorType":"error_code","errorClass":"ErrExec","httpStatus":null,"severity":"info","filePath":"tools/cmd/at/shell.go","lineNumber":28,"sourceCode":"\t\"path/filepath\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/kovidgoyal/kitty/tools/cli\"\n\t\"github.com/kovidgoyal/kitty/tools/cli/markup\"\n\t\"github.com/kovidgoyal/kitty/tools/tui/loop\"\n\t\"github.com/kovidgoyal/kitty/tools/tui/readline\"\n\t\"github.com/kovidgoyal/kitty/tools/utils\"\n\t\"github.com/kovidgoyal/kitty/tools/utils/shlex\"\n)\n\nvar _ = fmt.Print\n\nvar formatter *markup.Context\n\nconst prompt = \"🐱 \"\n\nvar ErrExec = errors.New(\"Execute command\")\n\nfunc shell_loop(rl *readline.Readline, kill_if_signaled bool) (int, error) {\n\tlp, err := loop.New(loop.NoAlternateScreen, loop.NoRestoreColors)\n\tif err != nil {\n\t\treturn 1, err\n\t}\n\trl.ChangeLoopAndResetText(lp)\n\n\tlp.OnInitialize = func() (string, error) {\n\t\trl.Start()\n\t\treturn \"\", nil\n\t}\n\tlp.OnFinalize = func() string { rl.End(); return \"\" }\n\n\tlp.OnResumeFromStop = func() error {\n\t\trl.Start()\n\t\treturn nil\n\t}","sourceCodeStart":10,"sourceCodeEnd":46,"githubUrl":"https://github.com/kovidgoyal/kitty/blob/6d5d0c440603ad9bdf6dcd599f73f6dde21acb44/tools/cmd/at/shell.go#L10-L46","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Recognize it as a sentinel: compare with errors.Is(err, ErrExec) and handle it by executing the pending command, not by reporting failure","If writing a custom loop around shell_loop, mirror the upstream shell_main handling of this return value","For debugging, read tools/cmd/at/shell.go's shell_main to see the intended control flow"],"exampleFix":"// before\nif err != nil { return err } // bubbles 'Execute command' up as a failure\n\n// after\nif errors.Is(err, ErrExec) {\n    // execute the command accumulated by the readline loop\n}\nif err != nil { return err }","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func isExecSignal(err error) bool {\n    return errors.Is(err, atshell.ErrExec)\n}","tryCatchPattern":"err := shell_main(...)\nif errors.Is(err, atshell.ErrExec) {\n    // execute the accumulated command; this is success, not failure\n} else if err != nil {\n    return err\n}","preventionTips":["Learn the sentinel's role in shell_main before wrapping the loop","Never stringify-and-log this error blindly; always errors.Is it","Keep upstream control-flow handling when embedding the at shell"],"tags":["kitty","sentinel-error","control-flow","go","shell"],"backgroundTag":"sentinel-error-control-flow","analyzedSha":"6d5d0c440603ad9bdf6dcd599f73f6dde21acb44","analyzedAt":"2026-08-27T14:20:20.142Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}