instructure/canvas-lms · critical · LlmConversation::Errors::ConversationError
Bearer token not configured for LLM Conversation Service
Error message
Bearer token not configured for LLM Conversation Service
What it means
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.
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.
Example fix
# before (config/credentials.yml or local) # llm_conversation_bearer_token: (absent) # after llm_conversation_bearer_token: "<service-bearer-token>" # or for initial-token mode: llm_conversation_service: initial_token: "<initial-token>"
Defensive patterns
Strategy: validation
Validate before calling
token = Rails.application.credentials.llm_conversation_bearer_token raise 'LLM bearer token not configured' if token.blank? client = LlmConversation::HttpClient.new(account: account)
Type guard
def client_ready?(client) !client.instance_variable_get(:@bearer_token).nil? rescue false end
Try / catch
begin
client.get(path)
rescue LlmConversation::Errors::ConversationError => e
if e.message.include?("Bearer token not configured")
raise ConfigError, "Set llm_conversation_bearer_token or provision v2 account tokens"
end
raise
end Prevention
- 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
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: "API key is required" / "API key not found" / "No API key was set": the missing-api-key error family across 16 libraries — this error's family across 16 libraries.
Related errors
- Cannot use initial token: account does not have…
- endpoint undefined
- No refresh token available for account
- Token refresh failed
- unknown format type: #
AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15).
Data as JSON: /api/errors/4fec2f598218ab2f.
Report an issue: GitHub.
Appendix: source
Thrown at lib/llm_conversation/http_client.rb:125
end
raise LlmConversation::Errors::ConversationError, base_url_error_message(region, test_cluster) if url.nil?
url
end
def base_url_error_message(region, test_cluster)
if test_cluster.present? && region.present?
"None of llm_conversation_base_url_beta_#{region}, llm_conversation_base_url_#{region}, or llm_conversation_base_url setting is configured"
elsif region.present?
"Neither llm_conversation_base_url_#{region} nor llm_conversation_base_url setting is configured"
else
"llm_conversation_base_url setting is not configured"
end
end
def request(method, path, payload: nil)
raise LlmConversation::Errors::ConversationError, "Bearer token not configured for LLM Conversation Service" if @bearer_token.nil?
uri = URI("#{@base_url}#{path}")
http = Net::HTTP.new(uri.host, uri.port)
if uri.scheme.casecmp?("https")
http.use_ssl = true
http.verify_mode = OpenSSL::SSL::VERIFY_PEER
end
headers = {
"Content-Type" => "application/json",
"Authorization" => "Bearer #{@bearer_token}",
"x-account-id" => @account&.uuid
}
req = case method
when :get
Net::HTTP::Get.new(uri.request_uri, headers)View on GitHub (pinned to 1c9f0bb801)