{"record":{"id":"8296e03600cc1c30","repo":"SeleniumHQ/selenium","slug":"callback-with-id-id-does-not-exist-for-event","errorCode":null,"errorMessage":"Callback with ID #{id} does not exist for event #{event}: #{ids}","messagePattern":"Callback with ID #(.+?) does not exist for event #(.+?): #(.+?)","errorType":"exception","errorClass":"Error::WebDriverError","httpStatus":null,"severity":"error","filePath":"rb/lib/selenium/webdriver/common/websocket_connection.rb","lineNumber":95,"sourceCode":"        @callbacks ||= Hash.new { |callbacks, event| callbacks[event] = [] }\n      end\n\n      def add_callback(event, &block)\n        @callbacks_mtx.synchronize do\n          callbacks[event] << block\n          block.object_id\n        end\n      end\n\n      def remove_callback(event, id)\n        @callbacks_mtx.synchronize do\n          return if @closing\n\n          callbacks_for_event = callbacks[event]\n          return if callbacks_for_event.reject! { |cb| cb.object_id == id }\n\n          ids = callbacks_for_event.map(&:object_id)\n          raise Error::WebDriverError, \"Callback with ID #{id} does not exist for event #{event}: #{ids}\"\n        end\n      end\n\n      def send_cmd(**payload)\n        id = next_id\n        data = payload.merge(id: id)\n        WebDriver.logger.debug \"WebSocket -> #{data}\"[...MAX_LOG_MESSAGE_SIZE], id: :ws\n        data = JSON.generate(data)\n        out_frame = WebSocket::Frame::Outgoing::Client.new(version: ws.version, data: data, type: 'text')\n\n        begin\n          socket.write(out_frame.to_s)\n        rescue *CONNECTION_ERRORS => e\n          raise e, \"WebSocket is closed (#{e.class}: #{e.message})\"\n        end\n\n        wait.until { @messages_mtx.synchronize { messages.delete(id) } }\n      end","sourceCodeStart":77,"sourceCodeEnd":113,"githubUrl":"https://github.com/SeleniumHQ/selenium/blob/aa36b38e696a0909e973bdf5e2f9031ffe842c4b/rb/lib/selenium/webdriver/common/websocket_connection.rb#L77-L113","documentation":"Raised by WebSocketConnection#remove_callback when no registered callback for the given event has an object_id matching the supplied id. The method uses reject! (which returns nil when nothing matched) to detect this, then raises Error::WebDriverError. It indicates the caller is trying to unregister a listener that was never added, was already removed, or whose id belongs to a different event.","triggerScenarios":"Calling connection.remove_callback(event, id) with an id not returned by add_callback for that same event; calling remove_callback twice with the same id; passing an id obtained from add_callback on a different event name; removing after close has started (though that path returns early).","commonSituations":"Storing the return value of add_callback incorrectly (e.g. discarding it and reconstructing an id), removing a BiDi/DevTools event subscription in a teardown block that runs more than once, unregistering a callback registered on a different WebSocketConnection instance, or a race where a callback was already removed by another code path.","solutions":["Capture and reuse the exact object_id returned by add_callback; only pass that value to remove_callback for the same event.","Guard the removal: track a flag or only call remove_callback once (e.g. set the id variable to nil after removing).","Verify the id is present in callbacks[event].map(&:object_id) before calling remove_callback, or rescue Error::WebDriverError around teardown."],"exampleFix":"// before\nid = conn.add_callback('log.entryAdded') { |e| handle(e) }\n# ... later, called twice or with wrong id\nconn.remove_callback('log.entryAdded', some_other_id)\n\n// after\n@cb_id = conn.add_callback('log.entryAdded') { |e| handle(e) }\n# remove exactly once, with the stored id and same event\nif @cb_id\n  conn.remove_callback('log.entryAdded', @cb_id)\n  @cb_id = nil\nend","handlingStrategy":"validation","validationCode":"# Only remove a callback once, with the exact id returned by add_callback\nreturn if @cb_id.nil?\nevent_callbacks = connection.callbacks[event]\nexists = event_callbacks.any? { |cb| cb.object_id == @cb_id }\nconnection.remove_callback(event, @cb_id) if exists\n@cb_id = nil","typeGuard":"def callback_registered?(connection, event, id)\n  return false unless id.is_a?(Integer)\n  connection.callbacks[event].any? { |cb| cb.object_id == id }\nend","tryCatchPattern":"begin\n  connection.remove_callback(event, @cb_id) if @cb_id\nrescue Selenium::WebDriver::Error::WebDriverError => e\n  WebDriver.logger.warn(\"callback already removed: #{e.message}\", id: :ws)\nensure\n  @cb_id = nil\nend","preventionTips":["Always store the object_id returned by add_callback and reuse that exact value for removal.","Set the stored id to nil immediately after a successful remove_callback to prevent double-removal.","Never reconstruct or guess an id; only use the value handed back by add_callback."],"tags":["websocket","bidi","callbacks","devtools"],"backgroundTag":null,"analyzedSha":"aa36b38e696a0909e973bdf5e2f9031ffe842c4b","analyzedAt":"2026-08-14T02:32:32.244Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}