yudai/gotty · error

unexpected zero length read from master

Error message

unexpected zero length read from master

What it means

WebTTY's handleMasterReadEvent() guards against empty reads from the PTY master side. A zero-length slice carries no message type byte, so it cannot be dispatched; the library treats it as an unexpected condition rather than silently ignoring it.

Source

Thrown at webtty/webtty.go:160

	return nil
}

func (wt *WebTTY) masterWrite(data []byte) error {
	wt.writeMutex.Lock()
	defer wt.writeMutex.Unlock()

	_, err := wt.masterConn.Write(data)
	if err != nil {
		return errors.Wrapf(err, "failed to write to master")
	}

	return nil
}

func (wt *WebTTY) handleMasterReadEvent(data []byte) error {
	if len(data) == 0 {
		return errors.New("unexpected zero length read from master")
	}

	switch data[0] {
	case Input:
		if !wt.permitWrite {
			return nil
		}

		if len(data) <= 1 {
			return nil
		}

		_, err := wt.slave.Write(data[1:])
		if err != nil {
			return errors.Wrapf(err, "failed to write received data to slave")
		}

	case Ping:

View on GitHub (pinned to a080c85cbc)

Solutions

  1. Check the wrapped PTY/slave reader: its Read must return io.EOF at end-of-stream, never (0, nil)
  2. Skip zero-length reads in custom slave implementations before invoking WebTTY
  3. If it happens at shutdown, treat it as benign teardown noise

Example fix

// before (custom slave Read)
return 0, nil
// after
if n == 0 && err == nil {
    return 0, io.EOF
}
return n, err
Defensive patterns

Strategy: type-guard

Validate before calling

if len(data) > 0 {
    if err := wt.handleMasterReadEvent(data); err != nil { ... }
}

Type guard

func readableData(data []byte) bool { return len(data) > 0 }

Try / catch

if err := runWebTTY(ctx); err != nil {
    if err.Error() == "unexpected zero length read from master" {
        // treat as EOF/teardown or wrap slave reads to skip empties
    }
}

Prevention

When it happens

Trigger: The master (PTY) read loop delivering a 0-byte data slice into handleMasterReadEvent — typically when a read returns success with no bytes or a caller passes an empty buffer.

Common situations: Race during PTY teardown where a read returns (nil/empty, nil); wrapping a slave whose Read returns 0, nil incorrectly (io.Reader implementations should return io.EOF instead of 0, nil).


AI-assisted analysis of yudai/gotty@a080c85cbc (2026-09-02). Data as JSON: /api/errors/70ab3c725e0d05f1. Report an issue: GitHub.