{"record":{"id":"e34ade7f37b5fa4a","repo":"instructure/canvas-lms","slug":"tool-key","errorCode":null,"errorMessage":"tool_key","messagePattern":"tool_key","errorType":"exception","errorClass":"ToolDisabled","httpStatus":null,"severity":"error","filePath":"app/services/study_assist.rb","lineNumber":103,"sourceCode":"    end\n\n    def initialize(course:, user:, prompt:, state:, locale: I18n.locale.to_s, regenerate: false)\n      @course = course\n      @user = user\n      @prompt = prompt.to_s\n      @state = state || {}\n      @locale = locale\n      @regenerate = regenerate || @prompt.match?(REGENERATE_PROMPT_PATTERN)\n    end\n\n    def call\n      return build_chips if @prompt.blank?\n\n      tool_key, tool_config = TOOLS.find { |_, cfg| @prompt.match?(cfg[:prompt_pattern]) }\n      raise InvalidPrompt, \"Unsupported prompt\" unless tool_key\n\n      unless @course.feature_enabled?(:study_assist) && @course.feature_enabled?(tool_config[:feature_flag])\n        raise ToolDisabled, tool_key.to_s\n      end\n\n      content = resolve_content\n\n      llm_config = LLMConfigs.config_for(tool_config[:llm_config])\n      raise \"No LLM config found for #{tool_config[:llm_config]}\" if llm_config.nil?\n\n      cache_key = response_cache_key(tool_key, llm_config, content)\n      Rails.cache.delete(cache_key) if @regenerate\n\n      Rails.cache.fetch(cache_key, expires_in: RESPONSE_CACHE_TTL) do\n        InstLLMHelper.with_rate_limit(user: @user, llm_config:) do\n          raw = call_cedar(tool_key, llm_config, content)\n          build_response(tool_key, raw)\n        end\n      end\n    rescue InstLLMHelper::RateLimitExceededError => e\n      Rails.logger.warn(\"Study Assist rate limit exceeded for #{tool_key}: #{e.message}\")","sourceCodeStart":85,"sourceCodeEnd":121,"githubUrl":"https://github.com/instructure/canvas-lms/blob/1c9f0bb8013ed69c4f2efe11fd483025469b7e6c/app/services/study_assist.rb#L85-L121","documentation":"StudyAssist::InvalidPrompt (raised as 'Unsupported prompt') is thrown by StudyAssist#call when the user's prompt does not match any registered tool's :prompt_pattern in the TOOLS hash. The service routes a free-form prompt to a specific AI tool; if no regex matches, it cannot proceed and raises before checking feature flags or calling the LLM.","triggerScenarios":"Calling StudyAssist.new(course:, user:, prompt: '<text>' ...).call where the prompt text matches none of the TOOLS regex prompt_patterns (e.g. vague or typo'd instructions, prompt for a tool not registered in TOOLS).","commonSituations":"Frontend sends a user prompt verbatim instead of a selected tool_key; new tool added to UI but prompt_pattern not registered in TOOLS; localized/translated prompts don't match English regex patterns; whitespace/casing differences defeat the regex.","solutions":["Check the prompt against the TOOLS prompt_pattern regexes in app/services/study_assist.rb and fix the wording to match","Register a new prompt_pattern in the TOOLS constant if this is a legitimate new intent","Have the caller pass an explicit tool selection instead of relying on prompt regex matching","Normalize the prompt (strip, downcase) before matching if the patterns expect canonical form"],"exampleFix":"// before\nStudyAssist.new(course: @course, user: @user, prompt: params[:prompt]).call\n// after\nprompt = params[:prompt].to_s.strip\nraise ActionController::BadRequest unless StudyAssist::TOOLS.any? { |_, cfg| prompt.match?(cfg[:prompt_pattern]) }\nStudyAssist.new(course: @course, user: @user, prompt: prompt).call","handlingStrategy":"validation","validationCode":"raise unless prompt.present? && StudyAssist::TOOLS.any? { |_, cfg| prompt.match?(cfg[:prompt_pattern]) }","typeGuard":"null","tryCatchPattern":"begin\n  StudyAssist.new(course:, user:, prompt:).call\nrescue StudyAssist::InvalidPrompt => e\n  render json: { error: e.message }, status: :bad_request\nend","preventionTips":["Keep prompt regexes in TOOLS and UI wording in sync","Normalize/strip prompt text before calling","Log unmatched prompts to detect missing patterns","Prefer explicit tool selection over prompt matching where possible"],"tags":["ruby","study-assist","prompt-routing","validation"],"backgroundTag":"invalid-argument-value","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"}