{"record":{"id":"964c130ed287b515","repo":"m1k1o/neko","slug":"target-stream-manager-does-not-support-moving-list","errorCode":null,"errorMessage":"target stream manager does not support moving listeners","messagePattern":"target stream manager does not support moving listeners","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/internal/capture/streamsink.go","lineNumber":255,"sourceCode":"\t// remove listener\n\tmanager.removeListener(listener)\n\n\t// stop if started\n\tmanager.stop()\n\n\treturn nil\n}\n\n// moving listeners between streams ensures, that target pipeline is running\n// before listener is added, and stops source pipeline if there are 0 listeners\nfunc (manager *StreamSinkManagerCtx) MoveListenerTo(listener types.SampleListener, stream types.StreamSinkManager) error {\n\tif listener == nil {\n\t\treturn errors.New(\"listener cannot be nil\")\n\t}\n\n\ttargetStream, ok := stream.(*StreamSinkManagerCtx)\n\tif !ok {\n\t\treturn errors.New(\"target stream manager does not support moving listeners\")\n\t}\n\n\t// we need to acquire both mutextes, from source stream and from target stream\n\t// in order to do that safely (without possibility of deadlock) we need third\n\t// global mutex, that ensures atomic locking\n\n\t// lock global mutex\n\tmoveSinkListenerMu.Lock()\n\n\t// lock source stream\n\tmanager.mu.Lock()\n\tdefer manager.mu.Unlock()\n\n\t// lock target stream\n\ttargetStream.mu.Lock()\n\tdefer targetStream.mu.Unlock()\n\n\t// unlock global mutex","sourceCodeStart":237,"sourceCodeEnd":273,"githubUrl":"https://github.com/m1k1o/neko/blob/b0f01cedea68893e85a3fd852c0521238c285695/server/internal/capture/streamsink.go#L237-L273","documentation":"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.","triggerScenarios":"Passing a stream manager that implements types.StreamSinkManager but is not *StreamSinkManagerCtx (a custom/mock/alternative implementation) as the target of MoveListenerTo.","commonSituations":"Tests using mock stream managers; alternative capture backends implementing the same interface; mixing screencast managers with stream-sink managers as move targets.","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"],"exampleFix":"// before\nif err := src.MoveListenerTo(listener, customManager); err != nil { return err }\n// after\ntarget, ok := customManager.(*capture.StreamSinkManagerCtx)\nif !ok {\n    _ = src.RemoveListener(listener)\n    return target.AddListener(listener) // fallback: manual move\n}\nreturn src.MoveListenerTo(listener, target)","handlingStrategy":"type-guard","validationCode":"target, ok := stream.(*capture.StreamSinkManagerCtx)\nif !ok {\n    return errors.New(\"target does not support listener moves; use remove+add\")\n}\nif err := manager.MoveListenerTo(listener, target); err != nil { return err }","typeGuard":"func supportsListenerMove(s types.StreamSinkManager) (*capture.StreamSinkManagerCtx, bool) {\n    ctx, ok := s.(*capture.StreamSinkManagerCtx)\n    return ctx, ok\n}","tryCatchPattern":"if err := manager.MoveListenerTo(listener, stream); err != nil {\n    if err.Error() == \"target stream manager does not support moving listeners\" {\n        // fallback: manual move\n        if err := manager.RemoveListener(listener); err != nil { return err }\n        return stream.AddListener(listener)\n    }\n    return err\n}","preventionTips":["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"],"tags":["go","streamsink","type-assertion","interface"],"backgroundTag":"unsupported-target-type","analyzedSha":"b0f01cedea68893e85a3fd852c0521238c285695","analyzedAt":"2026-09-01T10:35:56.638Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}