{"record":{"id":"092ebdf1fd664e43","repo":"kovidgoyal/kitty","slug":"the-ssh-kitten-is-meant-for-interactive-use-only","errorCode":null,"errorMessage":"The SSH kitten is meant for interactive use only, STDIN must be a terminal","messagePattern":"The SSH kitten is meant for interactive use only, STDIN must be a terminal","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kittens/ssh/main.go","lineNumber":873,"sourceCode":"\t\t\t\tfmt.Fprintln(os.Stderr, invargs.Msg)\n\t\t\t}\n\t\t\treturn 1, unix.Exec(SSHExe(), []string{\"ssh\"}, os.Environ())\n\t\t}\n\t\treturn 1, err\n\t}\n\tif passthrough {\n\t\treturn 1, unix.Exec(SSHExe(), utils.Concat([]string{\"ssh\"}, ssh_args, server_args), os.Environ())\n\t}\n\n\tctx, cancel := context.WithCancel(context.Background())\n\tdefer cancel()\n\tssh_config_channel := ReadSSHConfig(ctx, ssh_args, server_args[0])\n\n\tif os.Getenv(\"KITTY_WINDOW_ID\") == \"\" || os.Getenv(\"KITTY_PID\") == \"\" {\n\t\treturn 1, fmt.Errorf(\"The SSH kitten is meant to run inside a kitty window\")\n\t}\n\tif !tty.IsTerminal(os.Stdin.Fd()) {\n\t\treturn 1, fmt.Errorf(\"The SSH kitten is meant for interactive use only, STDIN must be a terminal\")\n\t}\n\treturn run_ssh(ssh_args, server_args, found_extra_args, ssh_config_channel)\n}\n\nfunc EntryPoint(parent *cli.Command) {\n\tcreate_cmd(parent, main)\n}\n\nfunc specialize_command(ssh *cli.Command) {\n\tssh.Usage = \"arguments for the ssh command\"\n\tssh.ShortDescription = \"Truly convenient SSH\"\n\tssh.HelpText = \"The ssh kitten is a thin wrapper around the ssh command. It automatically enables shell integration on the remote host, re-uses existing connections to reduce latency, makes the kitty terminfo database available, etc. Its invocation is identical to the ssh command. For details on its usage, see :doc:`/kittens/ssh`.\"\n\tssh.IgnoreAllArgs = true\n\tssh.OnlyArgsAllowed = true\n\tssh.ArgCompleter = cli.CompletionForWrapper(\"ssh\")\n}\n\nfunc test_integration_with_python(args []string) (rc int, err error) {","sourceCodeStart":855,"sourceCodeEnd":891,"githubUrl":"https://github.com/kovidgoyal/kitty/blob/6d5d0c440603ad9bdf6dcd599f73f6dde21acb44/kittens/ssh/main.go#L855-L891","documentation":"Immediately after the kitty-window check, the kitten verifies stdin is a TTY. Non-interactive stdin (pipe, file, closed) makes the interactive SSH session impossible, so it aborts.","triggerScenarios":"kitty +kitten ssh with stdin redirected from a pipe/file, run under a non-interactive context, or with stdin closed (<&-).","commonSituations":"Scripted use like echo cmd | kitty +kitten ssh host; cron jobs; running inside CI; chaining commands with pipes into the kitten.","solutions":["Don't pipe into the ssh kitten; use plain ssh host 'cmd' for non-interactive commands","Ensure the kitten runs with an interactive terminal on stdin","Check nothing in the shell chain closes or redirects stdin"],"exampleFix":"# before\necho ls | kitty +kitten ssh host\n\n# after\nkitty +kitten ssh host  # interactive\n# or for scripting:\nssh host ls","handlingStrategy":"validation","validationCode":"[ -t 0 ] || { echo 'stdin must be a terminal'; exit 1; }","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Use plain ssh for scripted/non-interactive commands","Never pipe stdin into kitty +kitten ssh"],"tags":["kitty","ssh","stdin","interactive"],"backgroundTag":"stdin-not-a-tty","analyzedSha":"6d5d0c440603ad9bdf6dcd599f73f6dde21acb44","analyzedAt":"2026-08-27T14:20:20.142Z","schemaVersion":2},"datasetVersion":"2026-08-27T19:17:21.184Z"}