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) } }
      end

View on GitHub (pinned to aa36b38e69)

Solutions

  1. Capture and reuse the exact object_id returned by add_callback; only pass that value to remove_callback for the same event.
  2. Guard the removal: track a flag or only call remove_callback once (e.g. set the id variable to nil after removing).
  3. 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

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


AI-assisted analysis of SeleniumHQ/selenium@aa36b38e69 (2026-08-14). Data as JSON: /api/errors/8296e03600cc1c30. Report an issue: GitHub.