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))
returnView on GitHub (pinned to bde624efd1)
Solutions
- Add the missing status case to the switch in lazyInitLocked with appropriate handling
- Grep for all streamStatus* constants and confirm every one is handled in the status switches
- 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
- Handle every streamStatus constant in every switch over s.status
- When adding a stream state, update lazyInitLocked and recvLoop/sendLoop switches together
- Run replication tests with -race when touching stream state transitions
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
- ExecutableTaskTracker encountered unknown task state: %v
- %w: state namespace failover version < ref namespace failove
- %w: state namespace failover version > ref namespace failove
- %w: state transition count < ref transition count: %v < %v
- ErrStaleState
AI-assisted analysis of temporalio/temporal@bde624efd1 (2026-09-01).
Data as JSON: /api/errors/884bc056c98fe1d3.
Report an issue: GitHub.