wavetermdev/waveterm · error

error setting terminal size: %w

Error message

error setting terminal size: %w

What it means

After checking for an active PTY, setTermSize_withlock calls pty.Setsize to apply the new window dimensions via TIOCSWINSZ. Any failure from that ioctl-style call (bad file descriptor because the process died, invalid size) is wrapped as this error.

Source

Thrown at pkg/jobmanager/jobcmd.go:167

	if jm.exitErr != nil {
		exitData.ExitErr = jm.exitErr.Error()
	}
	return true, exitData
}

func (jm *JobCmd) setTermSize_withlock(termSize waveobj.TermSize) error {
	if jm.cmdPty == nil {
		return fmt.Errorf("no active pty")
	}
	if jm.termSize.Rows == termSize.Rows && jm.termSize.Cols == termSize.Cols {
		return nil
	}
	err := pty.Setsize(jm.cmdPty, &pty.Winsize{
		Rows: uint16(termSize.Rows),
		Cols: uint16(termSize.Cols),
	})
	if err != nil {
		return fmt.Errorf("error setting terminal size: %w", err)
	}
	jm.termSize = termSize
	return nil
}

func (jm *JobCmd) SetTermSize(termSize waveobj.TermSize) error {
	jm.lock.Lock()
	defer jm.lock.Unlock()
	return jm.setTermSize_withlock(termSize)
}

// TODO set up a single input handler loop + queue so we dont need to hold the lock but still get synchronized in-order execution
func (jm *JobCmd) HandleInput(data wshrpc.CommandJobInputData) error {
	jm.lock.Lock()
	defer jm.lock.Unlock()

	if jm.cmd == nil || jm.cmdPty == nil {
		return fmt.Errorf("no active process")

View on GitHub (pinned to a4447c1563)

Solutions

  1. Clamp Rows/Cols to a sane range (1..65535, realistically < 1000) before sending.
  2. Treat the error as 'process gone' if the job exited concurrently and suppress it.
  3. Unwrap and check for EBADF/ENXIO to confirm the PTY is dead, then clean up the job.
  4. Retry the resize once after confirming the job is still running.

Example fix

// before
err := pty.Setsize(jm.cmdPty, &pty.Winsize{Rows: uint16(termSize.Rows), Cols: uint16(termSize.Cols)})
// after
if termSize.Rows <= 0 || termSize.Rows > 500 || termSize.Cols <= 0 || termSize.Cols > 500 {
    return fmt.Errorf("term size out of range")
}
err := pty.Setsize(jm.cmdPty, &pty.Winsize{Rows: uint16(termSize.Rows), Cols: uint16(termSize.Cols)})
Defensive patterns

Strategy: validation

Validate before calling

func clampTermSize(ts waveobj.TermSize) waveobj.TermSize {
    if ts.Rows < 1 { ts.Rows = 1 }
    if ts.Cols < 1 { ts.Cols = 1 }
    if ts.Rows > 500 { ts.Rows = 500 }
    if ts.Cols > 500 { ts.Cols = 500 }
    return ts
}

Type guard

func isResizableSize(ts waveobj.TermSize) bool {
    return ts.Rows > 0 && ts.Rows <= 500 && ts.Cols > 0 && ts.Cols <= 500
}

Try / catch

err := jobCmd.SetTermSize(termSize)
if err != nil {
    if strings.Contains(err.Error(), "error setting terminal size") {
        if exited, _ := jobCmd.GetExitInfo(); exited {
            return nil // pty closed due to exit; benign
        }
    }
    return err
}

Prevention

When it happens

Trigger: pty.Setsize fails while resizing: the PTY master's slave side closed (process exited between the check and the call), an invalid file descriptor, or a uint16 overflow from enormous Rows/Cols values.

Common situations: Resizing a terminal right as its job exits; client sending extreme sizes (> 65535) that overflow the uint16 cast; stale PTY handle after process teardown.

Related errors


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