{"record":{"id":"c745148df83e56f3","repo":"we-promise/sure","slug":"anthropic-base-url-must-be-an-http-s-url","errorCode":null,"errorMessage":"Anthropic Base URL must be an http(s) URL.","messagePattern":"Anthropic Base URL must be an http\\(s\\) URL\\.","errorType":"validation","errorClass":"Setting::ValidationError","httpStatus":422,"severity":"error","filePath":"app/controllers/settings/hostings_controller.rb","lineNumber":186,"sourceCode":"\n    if hosting_params.key?(:openai_model)\n      Setting.openai_model = hosting_params[:openai_model]\n    end\n\n    if hosting_params.key?(:openai_json_mode)\n      Setting.openai_json_mode = hosting_params[:openai_json_mode].presence\n    end\n\n    update_encrypted_setting(:anthropic_access_token)\n\n    if hosting_params.key?(:anthropic_base_url)\n      raw_base_url = hosting_params[:anthropic_base_url].to_s.strip\n      if raw_base_url.blank?\n        Setting.anthropic_base_url = nil\n      else\n        parsed = URI.parse(raw_base_url) rescue nil\n        unless parsed.is_a?(URI::HTTP)\n          raise Setting::ValidationError, t(\".invalid_anthropic_base_url\")\n        end\n        # A custom Anthropic-compatible endpoint requires a model — Provider::Anthropic\n        # raises without one. Validate the pair together (mirrors the OpenAI branch), using\n        # the submitted model when present so a blanked model field is caught too.\n        effective_model =\n          if hosting_params.key?(:anthropic_model)\n            hosting_params[:anthropic_model].to_s.strip\n          else\n            Setting.anthropic_model.to_s.strip\n          end\n        if effective_model.blank?\n          raise Setting::ValidationError, t(\".anthropic_model_required_for_base_url\")\n        end\n        Setting.anthropic_base_url = raw_base_url\n      end\n    end\n\n    if hosting_params.key?(:anthropic_model)","sourceCodeStart":168,"sourceCodeEnd":204,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/controllers/settings/hostings_controller.rb#L168-L204","documentation":"When the self-hosting settings form submits an anthropic_base_url, the controller strips it and runs URI.parse, then requires the result to be a URI::HTTP (or URI::HTTPS). Any other outcome — a different scheme or a scheme-less string that parses into URI::Generic — raises Setting::ValidationError with the translated '.invalid_anthropic_base_url' message. This guards Provider::Anthropic from being handed an endpoint it cannot call.","triggerScenarios":"Saving the Self-Hosting settings with anthropic_base_url set to a bare host (\"my-proxy.internal:8787\"), a missing scheme (\"api.litebbq.pro/v1\"), a wrong scheme (\"ftp://…\", \"anthropic://…\"), or trailing whitespace plus scheme-less input. Note the check is on URI class, not reachability: an unreachable but well-formed http:// URL passes this guard and fails later at request time.","commonSituations":"Pointing the app at an Anthropic-compatible proxy (LiteLLM, claude-code-proxy, a local docker container) and typing the host without http://; copying a URL that got truncated before the scheme; treating a Docker service name (\"http://ollama:11434\" works, \"ollama:11434\" does not) as a URL; curl-style muscle memory of omitting the scheme.","solutions":["Prefix the URL with a scheme: http://host[:port][/path] or https://host[/path]","Make sure there is no leading/trailing whitespace or invisible characters if you pasted the value (the code strips outer whitespace, but embedded characters still break the parse)","If you truly need a non-HTTP transport, this settings field cannot express it — put the conversion behind an http front-end","If the form still fails, reproduce in console: require \"uri\"; URI.parse(value).is_a?(URI::HTTP) to see exactly what parses"],"exampleFix":"# before\nSetting.anthropic_base_url = \"my-anthropic-proxy.internal:8080\"\n# => Setting::ValidationError: Anthropic Base URL must be an http(s) URL.\n\n# after\nSetting.anthropic_base_url = \"http://my-anthropic-proxy.internal:8080\"\n# => passes (URI::HTTP), and pair it with a non-blank anthropic_model","handlingStrategy":"validation","validationCode":"# Before saving hosting params\nrequire \"uri\"\n\ndef valid_http_url?(value)\n  parsed = URI.parse(value.to_s.strip) rescue nil\n  parsed.is_a?(URI::HTTP) && parsed.host.present?\nend\n\nurl = params[:anthropic_base_url].to_s.strip\nraise \"bad URL\" unless url.empty? || valid_http_url?(url)","typeGuard":"def http_url?(value)\n  URI.parse(value.to_s.strip).is_a?(URI::HTTP)\nrescue URI::Error, ArgumentError\n  false\nend","tryCatchPattern":"begin\n  # save hosting settings\nrescue Setting::ValidationError => e\n  # e.message is the translated field error: surface next to the Base URL input\n  flash.now[:alert] = e.message\nend","preventionTips":["Always type the scheme explicitly: http:// or https://","Prefer copy-paste of a working curl URL over retyping hosts","Validate with URI.parse(...).is_a?(URI::HTTP) in any script that writes this setting","Remember the check is format-only; test reachability separately (curl the endpoint)"],"tags":["rails","url-validation","settings","anthropic","self-hosting"],"backgroundTag":"invalid-url-format","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}