ankane/searchkick · error · Searchkick::Error
Pass `scroll` option to the search method for scrolling
Error message
Pass `scroll` option to the search method for scrolling
What it means
Searchkick's Results#scroll raises this when the result set has no scroll cursor. Elasticsearch scroll requires a keep-alive window passed to the original search call; only then does the response include `_scroll_id` (checked at results.rb:172-177). Calling `.scroll` on a query that was issued without the `scroll:` option has no cursor to continue, so Searchkick raises instead of sending an invalid scroll request.
Source
Thrown at lib/searchkick/results.rb:177
def with_score
return enum_for(:with_score) unless block_given?
with_hit.each do |result, hit|
yield result, hit["_score"]
end
end
def misspellings?
@options[:misspellings]
end
def scroll_id
@response["_scroll_id"]
end
def scroll
raise Error, "Pass `scroll` option to the search method for scrolling" unless scroll_id
if block_given?
records = self
while records.any?
yield records
records = records.scroll
end
records.clear_scroll
else
begin
# TODO Active Support notifications for this scroll call
params = {
scroll: options[:scroll],
body: {scroll_id: scroll_id}
}
Searchkick.add_opaque_id(params, options[:opaque_id]) if options[:opaque_id]
Results.new(@klass, Searchkick.client.scroll(params), @options)View on GitHub (pinned to 93e901a75b)
Solutions
- Pass the `scroll:` option to the search method itself: `Product.search('*', scroll: '1m')` - the response then carries `_scroll_id` and `.scroll` works
- Use the block form `Product.search('*', scroll: '1m').scroll { |batch| ... }` so batches are drained and the scroll context is cleared automatically
- If you only need pages rather than a stable cursor, keep using `page:`/`per_page:` instead of scroll
Example fix
# before
results = Product.search("milk")
next_batch = results.scroll # raises: no _scroll_id in the response
# after
results = Product.search("milk", scroll: "1m")
next_batch = results.scroll
# or let Searchkick drain and clear the cursor:
Product.search("milk", scroll: "1m").scroll { |batch| process(batch) } Defensive patterns
Strategy: validation
Validate before calling
# Route every full-dataset iteration through one helper that always issues the option
def search_scroll_all(model, term = "*", window: "1m", &block)
model.search(term, scroll: window).scroll(&block)
end
# or check the public cursor accessor before scrolling:
results = Product.search("*")
results.scroll { |b| process(b) } if results.scroll_id Try / catch
begin
batch = results.scroll
rescue Searchkick::Error => e
raise unless e.message.start_with?("Pass `scroll` option")
batch = Product.search(term, scroll: "1m") # reissue the search with scrolling enabled
end Prevention
- Always pair intent to iterate the full dataset with `scroll:` on the search call itself
- Wrap scroll iteration in a single helper so every scroll search is issued consistently
- Check `results.scroll_id` (public, results.rb:172) before calling `.scroll`
When it happens
Trigger: Calling `.scroll` on results of a search that lacked the option, e.g. `Product.search('*').scroll`. Passing `scroll:` as an argument to `.scroll` itself instead of to the `search` method. Copying scroll-iteration code onto a normal paginated search result.
Common situations: Adopting scroll for the first time from the Searchkick docs and putting the option on the wrong call; converting existing page/per_page code to scroll iteration; invoking the block form `results.scroll { |batch| ... }` on a non-scroll result.
Related errors
- No search context found for id
- Scroll id has expired
- Unknown model for index: #{index}. Pass the `models` option
- Multiple clients found - set Searchkick.client_type = :elast
- No client found - install the `elasticsearch` or `opensearch
AI-assisted analysis of ankane/searchkick@93e901a75b (2026-08-21).
Data as JSON: /api/errors/962fe9ed2922f194.
Report an issue: GitHub.