temporalio/temporal · error

upload stream unknown status

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.

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.

Appendix: 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)