wavetermdev/waveterm · error

invalid term size: %v

Error message

invalid term size: %v

What it means

MakeJobCmd validates the terminal size before spawning the job's command under a PTY. After substituting the default 25x80 when both are zero, any remaining non-positive Rows or Cols is rejected. This prevents passing an impossible window size to pty.StartWithSize.

Source

Thrown at pkg/jobmanager/jobcmd.go:53

	cleanedUp     bool
	ptyClosed     bool
	processExited bool
	exitCode      *int
	exitSignal    string
	exitErr       error
	exitTs        int64
}

func MakeJobCmd(jobId string, cmdDef CmdDef) (*JobCmd, error) {
	jm := &JobCmd{
		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()

View on GitHub (pinned to a4447c1563)

Solutions

  1. Ensure the caller supplies a positive TermSize (Rows > 0 and Cols > 0) in CommandJobStartData.
  2. If no real size is known, omit it (both 0) so the default 25x80 is applied.
  3. Clamp client-reported sizes before sending the RPC (max(1, size)).
  4. Validate TermSize at the RPC boundary with a guard before calling StartJob.

Example fix

// before
cmdDef.TermSize = waveobj.TermSize{Rows: -1, Cols: 80}
// after
cmdDef.TermSize = waveobj.TermSize{Rows: 25, Cols: 80} // or leave zeroed to get the default
Defensive patterns

Strategy: validation

Validate before calling

func validTermSize(ts waveobj.TermSize) bool {
    if ts.Rows == 0 && ts.Cols == 0 {
        return true // defaults will be applied
    }
    return ts.Rows > 0 && ts.Cols > 0
}

Type guard

func isPositiveSize(r, c int) bool { return r > 0 && c > 0 }

Try / catch

cmd, err := jobmanager.MakeJobCmd(jm, cmdDef)
if err != nil {
    if strings.Contains(err.Error(), "invalid term size") {
        cmdDef.TermSize = waveobj.TermSize{Rows: 25, Cols: 80}
        cmd, err = jobmanager.MakeJobCmd(jm, cmdDef)
    }
    if err != nil { return err }
}

Prevention

When it happens

Trigger: Starting a job (StartJob -> MakeJobCmd) with a CommandJobStart TermSize whose Rows or Cols is negative (or one is zero while the other isn't, so the zero-default doesn't apply).

Common situations: A client sending a partially-populated TermSize (e.g. Rows set, Cols zero, or negative values from a deserialization bug); a caller constructing CommandJobStartData manually without a valid TermSize.

Related errors


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