SeleniumHQ/selenium · error · Error::WebDriverError
Callback with ID #{id} does not exist for event #{event}: #{
Error message
Callback with ID #{id} does not exist for event #{event}: #{ids} What it means
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.
Source
Thrown at rb/lib/selenium/webdriver/common/websocket_connection.rb:95
@callbacks ||= Hash.new { |callbacks, event| callbacks[event] = [] }
end
def add_callback(event, &block)
@callbacks_mtx.synchronize do
callbacks[event] << block
block.object_id
end
end
def remove_callback(event, id)
@callbacks_mtx.synchronize do
return if @closing
callbacks_for_event = callbacks[event]
return if callbacks_for_event.reject! { |cb| cb.object_id == id }
ids = callbacks_for_event.map(&:object_id)
raise Error::WebDriverError, "Callback with ID #{id} does not exist for event #{event}: #{ids}"
end
end
def send_cmd(**payload)
id = next_id
data = payload.merge(id: id)
WebDriver.logger.debug "WebSocket -> #{data}"[...MAX_LOG_MESSAGE_SIZE], id: :ws
data = JSON.generate(data)
out_frame = WebSocket::Frame::Outgoing::Client.new(version: ws.version, data: data, type: 'text')
begin
socket.write(out_frame.to_s)
rescue *CONNECTION_ERRORS => e
raise e, "WebSocket is closed (#{e.class}: #{e.message})"
end
wait.until { @messages_mtx.synchronize { messages.delete(id) } }
endView on GitHub (pinned to aa36b38e69)
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.
Example fix
// before
id = conn.add_callback('log.entryAdded') { |e| handle(e) }
# ... later, called twice or with wrong id
conn.remove_callback('log.entryAdded', some_other_id)
// after
@cb_id = conn.add_callback('log.entryAdded') { |e| handle(e) }
# remove exactly once, with the stored id and same event
if @cb_id
conn.remove_callback('log.entryAdded', @cb_id)
@cb_id = nil
end Defensive patterns
Strategy: validation
Validate before calling
# Only remove a callback once, with the exact id returned by add_callback
return if @cb_id.nil?
event_callbacks = connection.callbacks[event]
exists = event_callbacks.any? { |cb| cb.object_id == @cb_id }
connection.remove_callback(event, @cb_id) if exists
@cb_id = nil Type guard
def callback_registered?(connection, event, id)
return false unless id.is_a?(Integer)
connection.callbacks[event].any? { |cb| cb.object_id == id }
end Try / catch
begin
connection.remove_callback(event, @cb_id) if @cb_id
rescue Selenium::WebDriver::Error::WebDriverError => e
WebDriver.logger.warn("callback already removed: #{e.message}", id: :ws)
ensure
@cb_id = nil
end Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Related errors
- BiDi connection is closed
- BiDi connection is not open
- Unable to find url to connect to from capabilities
- timeout must be a positive number
- interval must be a positive number
AI-assisted analysis of SeleniumHQ/selenium@aa36b38e69 (2026-08-14).
Data as JSON: /api/errors/8296e03600cc1c30.
Report an issue: GitHub.