temporalio/temporal · error

upload stream unknown status: %v

Error message

upload stream unknown status: %v

What it means

BiDirectionStreamImpl manages a replication stream with an internal status (created/open/closed). lazyInitLocked converts the current status into an action: open returns the stream, closed returns ErrClosed; any other status value means the status enum was extended without updating this switch, so it panics. It is a defensive default branch against unknown future states.

Source

Thrown at service/history/replication/bi_direction_stream.go:153

}

func (s *BiDirectionStreamImpl[Req, Resp]) lazyInitLocked() error {
	switch s.status {
	case streamStatusInitialized:
		streamingClient, err := s.clientProvider.Get(s.ctx)
		if err != nil {
			return err
		}
		s.streamingClient = streamingClient
		s.status = streamStatusOpen
		go s.recvLoop()
		return nil
	case streamStatusOpen:
		return nil
	case streamStatusClosed:
		return ErrClosed
	default:
		panic(fmt.Sprintf("upload stream unknown status: %v", s.status))
	}
}

func (s *BiDirectionStreamImpl[Req, Resp]) recvLoop() {
	defer close(s.channel)
	defer s.Close()

	for {
		resp, err := s.streamingClient.Recv()
		switch err {
		case nil:
			s.notifyRecvChannel(resp, nil)
		case io.EOF:
			return
		default:
			var errResp Resp
			s.notifyRecvChannel(errResp, NewStreamError("BiDirectionStream recv error", err))
			return

View on GitHub (pinned to bde624efd1)

Solutions

  1. Add the missing status case to the switch in lazyInitLocked with appropriate handling
  2. Grep for all streamStatus* constants and confirm every one is handled in the status switches
  3. If hit on an unmodified build, check for data races on BiDirectionStreamImpl (run with -race)

Example fix

// before
case streamStatusClosed:
  return ErrClosed
default:
  panic(fmt.Sprintf("upload stream unknown status: %v", s.status))
// after
case streamStatusClosed:
  return ErrClosed
case streamStatusInitializing: // newly added state
  return nil
default:
  panic(fmt.Sprintf("upload stream unknown status: %v", s.status))
Defensive patterns

Strategy: validation

Validate before calling

// When adding a stream status, verify all switches handle it:
// grep -rn "streamStatus" service/history/replication/ | grep -v _test
// ensure each switch on s.status includes the new constant

Type guard

func streamStatusKnown(s streamStatus) bool {
  switch s {
  case streamStatusCreated, streamStatusOpen, streamStatusClosed:
    return true
  }
  return false
}

Try / catch

// Panic path; wrap stream initialization:
func safeLazyInit(s *replication.BiDirectionStream[Req, Resp]) (err error) {
  defer func() { if r := recover(); r != nil { err = fmt.Errorf("stream status panic: %v", r) } }()
  return s.EnsureOpen()
}

Prevention

When it happens

Trigger: A new streamStatus constant added to the package but not handled in lazyInitLocked's switch; memory corruption or a race setting s.status to an invalid value.

Common situations: Hit almost exclusively when developing/forking temporal-server and adding a stream state (e.g. 'initializing') without updating lazyInitLocked; extremely rare in production builds.

Related errors


AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01). Data as JSON: /api/errors/884bc056c98fe1d3. Report an issue: GitHub.