instructure/canvas-lms · error · LlmConversation::Errors::ConversationError

Token refresh failed

Error message

Token refresh failed

What it means

refresh_v2_token! POSTs to the LLM conversation service's /token/refresh endpoint with the account's refresh JWT. If the HTTP response is not a Net::HTTPSuccess (2xx), the client raises ConversationError 'Token refresh failed', meaning the service refused to rotate the token pair.

Solutions

  1. Check the refresh endpoint response/status in logs to see why the service rejected it (expired token vs. server error).
  2. Re-provision the account's token pair: complete a fresh initial token exchange and store both api_jwt_token and refresh_jwt_token in account.settings, then clear LlmConversation::TokenCache for the account.
  3. Verify Setting llm_conversation_base_url_<region> points to the correct service environment and that the account UUID matches.
  4. Retry after confirming service health if the cause was a transient 5xx.

Example fix

# before (stale tokens)
# no re-provisioning; every request 401s, refresh also fails

# after: re-run token exchange, then
account.settings[:llm_conversation_service] = {
  api_jwt_token: new_result["api_token"],
  refresh_jwt_token: new_result["refresh_token"]
}
account.save!
LlmConversation::TokenCache.set_api_token(account, new_result["api_token"])
Defensive patterns

Strategy: retry

Validate before calling

# no pre-call validation possible; refresh failure is server-side
# ensure base URL and account uuid are correct first:
raise 'bad base url' unless Setting.get("llm_conversation_base_url_#{region}", nil).present?

Try / catch

begin
  client.get(path)
rescue LlmConversation::Errors::ConversationError => e
  if e.message == "Token refresh failed"
    LlmConversation::TokenService.reprovision!(account)
    retry
  end
  raise
end

Prevention

When it happens

Trigger: The /token/refresh call returns a non-2xx status — typically because the refresh JWT is expired or revoked, the x-account-id header does not match a known account, or the LLM conversation service itself is erroring.

Common situations: Refresh token expired after long inactivity (no grace refresh); tokens rotated elsewhere making the stored refresh token stale; service-side outage or 5xx; account UUID mismatch between Canvas and the LLM service; wrong llm_conversation_base_url Setting pointing at the wrong environment.

Related errors


AI-assisted analysis of instructure/canvas-lms@1c9f0bb801 (2026-09-15). Data as JSON: /api/errors/4c211c64f9648c43. Report an issue: GitHub.

Appendix: source

Thrown at lib/llm_conversation/http_client.rb:81

    def refresh_v2_token!
      refresh_token = @account.settings.dig(:llm_conversation_service, :refresh_jwt_token)
      raise LlmConversation::Errors::ConversationError, "No refresh token available for account" if refresh_token.blank?

      uri = URI("#{@base_url}/token/refresh")
      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

      req = Net::HTTP::Post.new(uri.request_uri,
                                "Content-Type" => "application/json",
                                "Authorization" => "Bearer #{refresh_token}",
                                "x-account-id" => @account.uuid)

      response = http.request(req)
      raise LlmConversation::Errors::ConversationError, "Token refresh failed" unless response.is_a?(Net::HTTPSuccess)

      result = JSON.parse(response.body)
      new_api_token = result["api_token"]
      new_refresh_token = result["refresh_token"]

      @account.settings[:llm_conversation_service] = {
        api_jwt_token: new_api_token,
        refresh_jwt_token: new_refresh_token
      }
      @account.save!

      LlmConversation::TokenCache.set_api_token(@account, new_api_token)
      @bearer_token = new_api_token
    end

    def resolve_base_url
      region = ApplicationController.region
      test_cluster = ApplicationController.test_cluster_name

View on GitHub (pinned to 1c9f0bb801)