{"record":{"id":"4fec2f598218ab2f","repo":"instructure/canvas-lms","slug":"bearer-token-not-configured-for-llm-conversation-service","errorCode":null,"errorMessage":"Bearer token not configured for LLM Conversation Service","messagePattern":"Bearer token not configured for LLM Conversation Service","errorType":"validation","errorClass":"LlmConversation::Errors::ConversationError","httpStatus":null,"severity":"critical","filePath":"lib/llm_conversation/http_client.rb","lineNumber":125,"sourceCode":"            end\n\n      raise LlmConversation::Errors::ConversationError, base_url_error_message(region, test_cluster) if url.nil?\n\n      url\n    end\n\n    def base_url_error_message(region, test_cluster)\n      if test_cluster.present? && region.present?\n        \"None of llm_conversation_base_url_beta_#{region}, llm_conversation_base_url_#{region}, or llm_conversation_base_url setting is configured\"\n      elsif region.present?\n        \"Neither llm_conversation_base_url_#{region} nor llm_conversation_base_url setting is configured\"\n      else\n        \"llm_conversation_base_url setting is not configured\"\n      end\n    end\n\n    def request(method, path, payload: nil)\n      raise LlmConversation::Errors::ConversationError, \"Bearer token not configured for LLM Conversation Service\" if @bearer_token.nil?\n\n      uri = URI(\"#{@base_url}#{path}\")\n      http = Net::HTTP.new(uri.host, uri.port)\n\n      if uri.scheme.casecmp?(\"https\")\n        http.use_ssl = true\n        http.verify_mode = OpenSSL::SSL::VERIFY_PEER\n      end\n\n      headers = {\n        \"Content-Type\" => \"application/json\",\n        \"Authorization\" => \"Bearer #{@bearer_token}\",\n        \"x-account-id\" => @account&.uuid\n      }\n\n      req = case method\n            when :get\n              Net::HTTP::Get.new(uri.request_uri, headers)","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/lib/llm_conversation/http_client.rb#L107-L143","documentation":"Every HttpClient#request call first checks that a bearer token was resolved. The constructor picks it from (in order) the credentials initial_token (v2 initial-token mode), TokenCache/v2 account tokens, or credentials.llm_conversation_bearer_token. If all of those resolve to nil, request raises ConversationError because it cannot set the Authorization header.","triggerScenarios":"Initializing the client without use_initial_token on an account without ai_experiences_v2_auth when credentials.llm_conversation_bearer_token is nil/empty; or calling get/post/patch/delete on a client whose token lookup returned nil (e.g. TokenCache.get_api_token returned nil and no refresh was attempted because the request guard fires first).","commonSituations":"Rails credentials missing the llm_conversation_bearer_token key in the environment (missing credentials.yml.local / RAILS_MASTER_KEY mismatch); account has v2 flag but its cached API token expired/was evicted and refresh token storage is broken; new environment or region where credentials were never configured.","solutions":["Set credentials.llm_conversation_bearer_token for non-v2 environments: rails credentials:edit, add llm_conversation_bearer_token: <token>.","For v2-auth accounts, ensure LlmConversation::TokenCache.get_api_token can resolve a token — provision the account's api_jwt_token/refresh_jwt_token in account.settings and warm the cache.","Confirm you are in the right Rails environment and that RAILS_MASTER_KEY / credentials file actually contains the LLM keys.","If using use_initial_token, verify Rails.application.credentials.dig(:llm_conversation_service, :initial_token) is set — otherwise the resolved token is nil and every request raises."],"exampleFix":"# before (config/credentials.yml or local)\n# llm_conversation_bearer_token: (absent)\n\n# after\nllm_conversation_bearer_token: \"<service-bearer-token>\"\n# or for initial-token mode:\nllm_conversation_service:\n  initial_token: \"<initial-token>\"","handlingStrategy":"validation","validationCode":"token = Rails.application.credentials.llm_conversation_bearer_token\nraise 'LLM bearer token not configured' if token.blank?\nclient = LlmConversation::HttpClient.new(account: account)","typeGuard":"def client_ready?(client)\n  !client.instance_variable_get(:@bearer_token).nil?\nrescue\n  false\nend","tryCatchPattern":"begin\n  client.get(path)\nrescue LlmConversation::Errors::ConversationError => e\n  if e.message.include?(\"Bearer token not configured\")\n    raise ConfigError, \"Set llm_conversation_bearer_token or provision v2 account tokens\"\n  end\n  raise\nend","preventionTips":["Add a boot-time or health-check assertion that the LLM credentials exist per environment","Document required credential keys (llm_conversation_bearer_token, llm_conversation_service.initial_token) in deploy runbooks","For v2 accounts, keep TokenCache warm and validate token provisioning after flag enablement","Verify RAILS_MASTER_KEY is set when credentials appear missing in an environment"],"tags":["ruby","auth","credentials","config","http-client"],"backgroundTag":"missing-api-key","analyzedSha":"1c9f0bb8013ed69c4f2efe11fd483025469b7e6c","analyzedAt":"2026-09-15T20:33:18.891Z","contentChangedAt":"2026-09-15T20:33:18.891Z","schemaVersion":2},"datasetVersion":"2026-09-23T02:17:17.105Z"}