{"record":{"id":"8ef7d0f06bf83f1d","repo":"ankane/searchkick","slug":"unknown-keywords-unknown-keywords-join-8ef7d0","errorCode":null,"errorMessage":"unknown keywords: #{unknown_keywords.join(\", \")}","messagePattern":"unknown keywords: #(.+?)","errorType":"exception","errorClass":"ArgumentError","httpStatus":null,"severity":"error","filePath":"lib/searchkick/query.rb","lineNumber":33,"sourceCode":"      :offset_value, :offset, :previous_page, :prev_page, :next_page, :first_page?, :last_page?,\n      :out_of_range?, :hits, :response, :to_a, :first, :scroll, :highlights, :with_highlights,\n      :with_score, :misspellings?, :scroll_id, :clear_scroll, :missing_records, :with_hit\n\n    def initialize(klass, term = \"*\", **options)\n      if options[:conversions]\n        Searchkick.warn(\"The `conversions` option is deprecated in favor of `conversions_v2`, which provides much better search performance. Upgrade to `conversions_v2` or rename `conversions` to `conversions_v1`\")\n      end\n\n      if options.key?(:conversions_v1)\n        options[:conversions] = options.delete(:conversions_v1)\n      end\n\n      unknown_keywords = options.keys - [:aggs, :block, :body, :body_options, :boost,\n        :boost_by, :boost_by_distance, :boost_by_recency, :boost_where, :conversions, :conversions_v2, :conversions_term, :debug, :emoji, :exclude, :explain,\n        :fields, :highlight, :includes, :index_name, :indices_boost, :knn, :limit, :load,\n        :match, :misspellings, :models, :model_includes, :offset, :opaque_id, :operator, :order, :padding, :page, :per_page, :profile,\n        :request_params, :routing, :scope_results, :scroll, :select, :similar, :smart_aggs, :suggest, :total_entries, :track, :type, :where]\n      raise ArgumentError, \"unknown keywords: #{unknown_keywords.join(\", \")}\" if unknown_keywords.any?\n\n      term = term.to_s\n\n      if options[:emoji]\n        term = EmojiParser.parse_unicode(term) { |e| \" #{e.name.tr('_', ' ')} \" }.strip\n      end\n\n      @klass = klass\n      @term = term\n      @options = options\n      @match_suffix = options[:match] || searchkick_options[:match] || \"analyzed\"\n\n      # prevent Ruby warnings\n      @type = nil\n      @routing = nil\n      @misspellings = false\n      @misspellings_below = nil\n      @highlighted_fields = nil","sourceCodeStart":15,"sourceCodeEnd":51,"githubUrl":"https://github.com/ankane/searchkick/blob/93e901a75b11a25101668a616e006b158251b16e/lib/searchkick/query.rb#L15-L51","documentation":"Like the model macro, `Model.search(term, **options)` validates its keywords against a query-time allow-list (`:where, :order, :fields, :boost_by, :misspellings, :aggs, ...`). Unknown keys are collected and raised as one `ArgumentError` before any request is sent. Typical causes: model-level options passed at query time, misspellings, or options that only exist in other searchkick versions.","triggerScenarios":"`Product.search(\"milk\", batch_size: 100)` (model option), `Product.search(\"milk\", filter: {...})` (meant `where:`), `Product.search(\"milk\", lang: \"en\")`, or any option added/removed across gem versions — raises at query construction, so the first search after deploy fails.","commonSituations":"Upgrading searchkick majors where options were renamed (e.g. old `misspellings: false` stays valid but others change); copying search calls from README HEAD on an older gem; confusing model-level options (`searchable`, `word_start`, `stemmer`) with query-level ones; passing a typo'd `where` as `whare`.","solutions":["Compare the listed unknown keys against the allow-list in lib/searchkick/query.rb:23-27 and fix names/locations.","Move model-level options (`batch_size`, `searchable`, `filterable`, `language`, ...) into the `searchkick` macro; keep only query-shaping options in `.search`.","Verify the option exists in your gem version (`Searchkick::VERSION` + the matching README tag on GitHub) and pin the Gemfile accordingly.","Smoke-test one representative `.search` call in a staging console right after gem upgrades."],"exampleFix":"# before\nProduct.search(\"milk\", filter: {in_stock: true}, page: 1)\n# => ArgumentError: unknown keywords: filter\n\n# after\nProduct.search(\"milk\", where: {in_stock: true}, page: 1)","handlingStrategy":"validation","validationCode":"QUERY_OPTIONS = %i[where order fields limit offset page per_page aggs boost_by misspellings operator highlight load includes exclude similar select body body_options index_name models knn].freeze\ndef product_search(term = \"*\", **opts)\n  unknown = opts.keys - QUERY_OPTIONS\n  raise ArgumentError, \"unknown search options: #{unknown.join(', ')}\" if unknown.any?\n  Product.search(term, **opts)\nend","typeGuard":"def valid_search_options?(opts)\n  allowed = %i[aggs block body body_options boost boost_by boost_by_distance boost_by_recency boost_where conversions conversions_v2 conversions_term debug emoji exclude explain fields highlight includes index_name indices_boost knn limit load match misspellings models model_includes offset opaqueid operator order padding page per_page profile request_params routing scope_results scroll select similar smart_aggs suggest total_entries track type where]\n  (opts.keys - allowed).empty?\nend","tryCatchPattern":null,"preventionTips":["Route all searches through a thin wrapper that validates option keys, so typos fail loudly and centrally.","After gem upgrades, grep the codebase for `.search(` usages with newly invalid options.","Smoke-test one search per model in the staging console after every searchkick version bump."],"tags":["searchkick","options","query","argumenterror"],"backgroundTag":"unknown-option-keyword","analyzedSha":"93e901a75b11a25101668a616e006b158251b16e","analyzedAt":"2026-08-21T19:06:23.767Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}