wavetermdev/waveterm · error

failed to start command: %w

Error message

failed to start command: %w

What it means

After building the exec.Command, MakeJobCmd calls pty.StartWithSize to spawn the process attached to a pseudo-terminal. Any failure from that call (command not found, permission denied, PTY allocation exhaustion) is wrapped as this error.

Source

Thrown at pkg/jobmanager/jobcmd.go:64

		jobId: jobId,
	}
	if cmdDef.TermSize.Rows == 0 || cmdDef.TermSize.Cols == 0 {
		cmdDef.TermSize.Rows = 25
		cmdDef.TermSize.Cols = 80
	}
	if cmdDef.TermSize.Rows <= 0 || cmdDef.TermSize.Cols <= 0 {
		return nil, fmt.Errorf("invalid term size: %v", cmdDef.TermSize)
	}
	ecmd := exec.Command(cmdDef.Cmd, cmdDef.Args...)
	if len(cmdDef.Env) > 0 {
		ecmd.Env = make([]string, 0, len(cmdDef.Env))
		for key, val := range cmdDef.Env {
			ecmd.Env = append(ecmd.Env, fmt.Sprintf("%s=%s", key, val))
		}
	}
	cmdPty, err := pty.StartWithSize(ecmd, &pty.Winsize{Rows: uint16(cmdDef.TermSize.Rows), Cols: uint16(cmdDef.TermSize.Cols)})
	if err != nil {
		return nil, fmt.Errorf("failed to start command: %w", err)
	}
	unixutil.SetCloseOnExec(int(cmdPty.Fd()))
	jm.cmd = ecmd
	jm.cmdPty = cmdPty
	jm.ptsName = jm.cmdPty.Name()
	jm.termSize = cmdDef.TermSize
	go jm.waitForProcess()
	return jm, nil
}

func (jm *JobCmd) waitForProcess() {
	if jm.cmd == nil || jm.cmd.Process == nil {
		return
	}
	err := jm.cmd.Wait()
	jm.lock.Lock()
	defer jm.lock.Unlock()

View on GitHub (pinned to a4447c1563)

Solutions

  1. Unwrap the error and check for exec.ErrNotFound / 'permission denied'; fix the Cmd path or chmod +x the binary.
  2. Verify the command exists in PATH within the job manager's environment.
  3. Check pty availability: raise ulimit / kernel pty limits if 'out of pty' errors appear.
  4. Retry StartJob after fixing the environment if it was transient resource exhaustion.

Example fix

// before
Cmd: "node serve.js"
// after
Cmd: "/usr/local/bin/node", Args: ["serve.js"] // absolute path to an executable binary
Defensive patterns

Strategy: validation

Validate before calling

// verify the binary is executable before starting the job
if _, err := exec.LookPath(cmdDef.Cmd); err != nil {
    return fmt.Errorf("command %q not found in PATH: %w", cmdDef.Cmd, err)
}

Type guard

func cmdStartable(cmd string, args []string) bool {
    _, err := exec.LookPath(cmd)
    return err == nil
}

Try / catch

cmdPty, err := pty.StartWithSize(ecmd, size)
if err != nil {
    if errors.Is(err, exec.ErrNotFound) || errors.Is(err, os.ErrPermission) {
        // surface a clear 'bad command' message to the user
    }
    return fmt.Errorf("failed to start command: %w", err)
}

Prevention

When it happens

Trigger: StartJob with a Cmd that doesn't exist in PATH, isn't executable, an Env list that breaks exec, or the OS can't allocate a new PTY (out of file descriptors / no free ptys).

Common situations: Typo in the command name in the job start RPC; running in a container without PTY support or with a low ulimit on ptys; command binary missing after a deployment change.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/9131a7bf192b5b8c. Report an issue: GitHub.