we-promise/sure · error · Setting::ValidationError

%{field} must be a whole number ≥ %{minimum}.

Error message

%{field} must be a whole number ≥ %{minimum}.

What it means

The hosting controller validates four numeric LLM settings — llm_context_window (min 256), llm_max_response_tokens (min 64), llm_max_items_per_call (min 1), and ai_response_timeout (min Chat::MIN_RESPONSE_TIMEOUT) — by parsing the raw param with Integer(raw, 10) and comparing against LLM_NUMERIC_MINIMUMS. Anything that strict base-10 Integer() rejects (decimals, exponent notation, thousands separators, hex) parses to nil, and values below the minimum fail the comparison; both raise Setting::ValidationError with '.invalid_llm_budget'. A blank value is valid and clears the setting (sets it to nil), so the error only fires on present-but-wrong input. The constants mirror the min: attributes on the form inputs so server-side rejects what the browser validator would.

Source

Thrown at app/controllers/settings/hostings_controller.rb:225

    if hosting_params.key?(:llm_provider)
      provider = hosting_params[:llm_provider].to_s
      if %w[openai anthropic].include?(provider)
        Setting.llm_provider = provider
      end
    end

    LLM_NUMERIC_MINIMUMS.each do |key, minimum|
      next unless hosting_params.key?(key)
      raw = hosting_params[key].to_s.strip
      if raw.blank?
        Setting.public_send("#{key}=", nil)
        next
      end
      parsed = Integer(raw, 10) rescue nil
      if parsed.nil? || parsed < minimum
        label = t("settings.hostings.openai_settings.#{key}_label")
        raise Setting::ValidationError, t(".invalid_llm_budget", field: label, minimum: minimum)
      end
      Setting.public_send("#{key}=", parsed)
    end

    if hosting_params.key?(:external_assistant_url)
      Setting.external_assistant_url = hosting_params[:external_assistant_url]
    end

    update_encrypted_setting(:external_assistant_token)

    if hosting_params.key?(:external_assistant_agent_id)
      Setting.external_assistant_agent_id = hosting_params[:external_assistant_agent_id]
    end

    update_assistant_type

    redirect_to settings_hosting_path, notice: t(".success")
  rescue Setting::ValidationError => error

View on GitHub (pinned to e69894adb9)

Solutions

  1. Enter a plain base-10 integer with no unit, comma, or decimal point (e.g. 200000 not "200K", 30 not "30s")
  2. Raise the value to the field minimum: context window ≥ 256, max response tokens ≥ 64, max items per call ≥ 1, response timeout ≥ Chat::MIN_RESPONSE_TIMEOUT
  3. To unset a budget, leave the field empty rather than typing 0 — blank clears the setting, 0 raises
  4. Check app/controllers/settings/hostings_controller.rb:7 LLM_NUMERIC_MINIMUMS for the authoritative floors if the form's error message is unclear

Example fix

# before
Setting.llm_context_window = "128k"   # or 100, or "1,024"
# => Setting::ValidationError: Context Window must be a whole number ≥ 256.

# after
Setting.llm_context_window = 128000    # plain Integer, ≥ minimum
Setting.llm_context_window = ""         # also valid: clears the setting (nil)
Defensive patterns

Strategy: validation

Validate before calling

MINS = { llm_context_window: 256, llm_max_response_tokens: 64,
          llm_max_items_per_call: 1, ai_response_timeout: Chat::MIN_RESPONSE_TIMEOUT.to_i }

value = params[:llm_context_window].to_s.strip
parsed = Integer(value, 10) rescue nil
ok = value.empty? || (parsed && parsed >= MINS[:llm_context_window])
raise "out of range" unless ok

Type guard

def valid_llm_integer?(raw, minimum)
  return true if raw.to_s.strip.empty? # blank clears
  parsed = Integer(raw.to_s.strip, 10) rescue nil
  !parsed.nil? && parsed >= minimum
end

Try / catch

rescue Setting::ValidationError => e
  # message already includes the field label and minimum; re-render form with values
  render :edit, status: :unprocessable_entity
end

Prevention

When it happens

Trigger: Typing 10.5 or 1e3 into the context-window field; entering 100 for llm_context_window (below 256); 32 for llm_max_response_tokens (below 64); 0 for llm_max_items_per_call; "1,024" with a comma; "128 " is fine after strip but "12 8" is not; submitting "5s" or "30sec" for ai_response_timeout instead of a bare number; a curl/API client bypassing the form's min validation entirely.

Common situations: Copy-pasting model spec-sheet numbers like "200K" or "128k tokens" with a unit suffix; assuming the timeout field takes seconds-with-unit like Docker or nginx configs; automated config management (Ansible/terraform templates) rendering quoted decimals; browser validation skipped because the form was submitted programmatically.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/171cf9de2d0fdc84. Report an issue: GitHub.