argoproj/argo-workflows · error
cannot convert stdin to os.File, it was %T
Error message
cannot convert stdin to os.File, it was %T
What it means
StartCommand uses a pty to run a command with a TTY when stdin should be interactive. pty.Start requires cmd.Stdin to be an *os.File (a real file descriptor); if the Stdin field holds any other io.Reader implementation, it cannot be attached to the pty and this error is returned. The code comment notes it 'should never happen when stdin is a tty', so it indicates a non-file stdin was configured.
Source
Thrown at workflow/executor/osspecific/command_unix.go:45
}
cmd.SysProcAttr = &syscall.SysProcAttr{}
if !isTerminal(cmd.Stdin) {
// avoid the error "Inappropriate ioctl for device" when
// running in tty
//
// pty.Start uses setsid internally, which makes the process
// the group leader already
Setpgid(cmd.SysProcAttr)
return simpleStart(cmd)
}
stdin, ok := cmd.Stdin.(*os.File)
if !ok {
// should never happen when stdin is a tty
return nil, fmt.Errorf("cannot convert stdin to os.File, it was %T", cmd.Stdin)
}
stdout := cmd.Stdout
stderr := cmd.Stderr
// pty.Start will not assign these to the pty unless they are nil
cmd.Stdin = nil
cmd.Stdout = nil
cmd.Stderr = nil
ptmx, err := pty.Start(cmd)
if err != nil {
return nil, err
}
// Handle pty size
sigWinchCh := make(chan os.Signal, 1)
signal.Notify(sigWinchCh, syscall.SIGWINCH)View on GitHub (pinned to 35bff19146)
Solutions
- Set cmd.Stdin to an *os.File — open /dev/null or the real terminal file: `f, _ := os.Open("/dev/null"); cmd.Stdin = f`
- If stdin is a pipe or wrapped reader, use simpleStart (the non-pty path) instead of the pty path
- If you need to feed in-memory data over a pty, write it to an os.Pipe and pass the *os.File read end as cmd.Stdin
- Remove intermediate wrappers (bufio.Reader, io.MultiReader) and pass the raw file
Example fix
// before
cmd.Stdin = bytes.NewBufferString(input)
// after
r, w, _ := os.Pipe()
go func() { w.WriteString(input); w.Close() }()
cmd.Stdin = r // *os.File Defensive patterns
Strategy: type-guard
Validate before calling
if _, ok := cmd.Stdin.(*os.File); !ok {
return simpleStart(cmd) // or replace Stdin with an *os.File
} Type guard
func isFileStdin(cmd *exec.Cmd) bool {
_, ok := cmd.Stdin.(*os.File)
return ok
} Try / catch
if err := StartCommand(ctx, cmd); err != nil && strings.Contains(err.Error(), "cannot convert stdin to os.File") {
return simpleStart(cmd) // fallback to non-pty start
} Prevention
- Only assign *os.File values (os.Pipe ends, /dev/null, terminal fd) to cmd.Stdin
- Avoid wrapping stdin in bufio.Reader / bytes.Buffer when using pty starts
- Route in-memory input through os.Pipe
When it happens
Trigger: Calling StartCommand with a cmd whose Stdin was set to something other than *os.File — e.g. bytes.Buffer, os.Pipe read end is fine but a wrapped reader, strings.Reader, net.Conn, or nil-cast generic io.Reader is not.
Common situations: Custom executor code or tests constructing exec.Cmd with an in-memory reader and requesting a TTY/interactive start; code paths that wrap stdin in a bufio.Reader or io.LimitReader; running where the caller assumed pty.Start would fall back gracefully.
Related errors
- failed to open process: %w
- failed to load CtrlRoutine: %w
- failed to open remote thread in target process %d: %w
- failed to read container args file %s: %w
- failed to unmarshal container args: %w
AI-assisted analysis of argoproj/argo-workflows@35bff19146 (2026-09-03).
Data as JSON: /api/errors/53b4fabd07948f47.
Report an issue: GitHub.