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 mutex

View on GitHub (pinned to b0f01cedea)

Solutions

  1. Pass a *StreamSinkManagerCtx (the manager obtained from the same capture package) as the target
  2. Implement MoveListenerTo support in your custom manager type, or handle the error with a manual remove+add
  3. 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

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


AI-assisted analysis of m1k1o/neko@b0f01cedea (2026-09-01). Data as JSON: /api/errors/964c130ed287b515. Report an issue: GitHub.