m1k1o/neko · error
target stream manager does not support moving listeners
Error message
target stream manager does not support moving listeners
What it means
MoveListenerTo accepts a types.StreamSinkManager and type-asserts it to *StreamSinkManagerCtx. If the target stream is a different implementation, the assertion fails and it returns 'target stream manager does not support moving listeners'. Only the concrete StreamSinkManagerCtx supports cross-stream listener moves.
Source
Thrown at server/internal/capture/streamsink.go:255
// remove listener
manager.removeListener(listener)
// stop if started
manager.stop()
return nil
}
// moving listeners between streams ensures, that target pipeline is running
// before listener is added, and stops source pipeline if there are 0 listeners
func (manager *StreamSinkManagerCtx) MoveListenerTo(listener types.SampleListener, stream types.StreamSinkManager) error {
if listener == nil {
return errors.New("listener cannot be nil")
}
targetStream, ok := stream.(*StreamSinkManagerCtx)
if !ok {
return errors.New("target stream manager does not support moving listeners")
}
// we need to acquire both mutextes, from source stream and from target stream
// in order to do that safely (without possibility of deadlock) we need third
// global mutex, that ensures atomic locking
// lock global mutex
moveSinkListenerMu.Lock()
// lock source stream
manager.mu.Lock()
defer manager.mu.Unlock()
// lock target stream
targetStream.mu.Lock()
defer targetStream.mu.Unlock()
// unlock global mutexView on GitHub (pinned to b0f01cedea)
Solutions
- Pass a *StreamSinkManagerCtx (the manager obtained from the same capture package) as the target
- Implement MoveListenerTo support in your custom manager type, or handle the error with a manual remove+add
- Use interface checks or a capability query before attempting a move
Example fix
// before
if err := src.MoveListenerTo(listener, customManager); err != nil { return err }
// after
target, ok := customManager.(*capture.StreamSinkManagerCtx)
if !ok {
_ = src.RemoveListener(listener)
return target.AddListener(listener) // fallback: manual move
}
return src.MoveListenerTo(listener, target) Defensive patterns
Strategy: type-guard
Validate before calling
target, ok := stream.(*capture.StreamSinkManagerCtx)
if !ok {
return errors.New("target does not support listener moves; use remove+add")
}
if err := manager.MoveListenerTo(listener, target); err != nil { return err } Type guard
func supportsListenerMove(s types.StreamSinkManager) (*capture.StreamSinkManagerCtx, bool) {
ctx, ok := s.(*capture.StreamSinkManagerCtx)
return ctx, ok
} Try / catch
if err := manager.MoveListenerTo(listener, stream); err != nil {
if err.Error() == "target stream manager does not support moving listeners" {
// fallback: manual move
if err := manager.RemoveListener(listener); err != nil { return err }
return stream.AddListener(listener)
}
return err
} Prevention
- Only pass managers created by the capture package's StreamSinkManagerCtx to MoveListenerTo
- Do a type assertion before calling when the manager type is dynamic
- Provide a remove+add fallback for non-native manager implementations
- In tests, use the real StreamSinkManagerCtx rather than mocks when exercising MoveListenerTo
When it happens
Trigger: Passing a stream manager that implements types.StreamSinkManager but is not *StreamSinkManagerCtx (a custom/mock/alternative implementation) as the target of MoveListenerTo.
Common situations: Tests using mock stream managers; alternative capture backends implementing the same interface; mixing screencast managers with stream-sink managers as move targets.
Related errors
- not a valid plugin
- listener cannot be nil
- image data not found
- screencast not enabled
- unable to get first image
AI-assisted analysis of m1k1o/neko@b0f01cedea (2026-09-01).
Data as JSON: /api/errors/964c130ed287b515.
Report an issue: GitHub.