{"record":{"id":"14cca04d343ea218","repo":"we-promise/sure","slug":"unknown-record-type","errorCode":null,"errorMessage":"Unknown record type","messagePattern":"Unknown record type","errorType":"http","errorClass":"ActiveRecord::RecordNotFound","httpStatus":404,"severity":"warning","filePath":"app/controllers/settings/background_jobs_controller.rb","lineNumber":60,"sourceCode":"    end\n  end\n\n  private\n    # Resolves record_type without reflecting on request input: rather than\n    # turning the param into a constant, look the id up in each cancellable\n    # base table and require the claimed type to match the found record's\n    # class (or its base class). STI subclass names are still accepted —\n    # the UI sends base_class names, but a direct request naming e.g.\n    # TransactionImport shouldn't 404.\n    def find_record!\n      claimed_type = params[:record_type].to_s\n\n      CANCELLABLE_BASE_TYPES.each do |base|\n        record = base.find_by(id: params[:id])\n        return record if record && [ record.class.name, base.name ].include?(claimed_type)\n      end\n\n      raise ActiveRecord::RecordNotFound, \"Unknown record type\"\n    end\n\n    # User-facing: surfaces as the failed operation's error in the family UI.\n    def cancelled_error_message\n      t(\"settings.background_jobs.cancel.cancelled_error\")\n    end\n\n    def cancellable_status?(record)\n      case record\n      when Sync then record.in_progress?\n      when Import then record.importing? || record.reverting?\n      when ImportSession then record.importing?\n      when FamilyExport then record.pending? || record.processing?\n      end\n    end\n\n    def apply_cancel!(record)\n      case record","sourceCodeStart":42,"sourceCodeEnd":78,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/controllers/settings/background_jobs_controller.rb#L42-L78","documentation":"Settings::BackgroundJobsController#cancel resolves the target record via find_record!: it iterates the four cancellable base types (Sync, Import, ImportSession, FamilyExport), looks up params[:id] in each table, and requires params[:record_type] to equal either the found record's class name or the base class name (STI-safe). If no (id, claimed type) pair matches, it raises ActiveRecord::RecordNotFound(\"Unknown record type\"), which Rails renders as a 404. The design is deliberate: the param is never turned into a constant, so arbitrary class names cannot be reflected on.","triggerScenarios":"POST/PATCH to settings background_jobs cancel path with a record_type that is not one of \"Sync\"/\"Import\"/\"ImportSession\"/\"FamilyExport\" (or an STI subclass name like \"TransactionImport\"), a record_type that doesn't match the table where params[:id] actually lives (e.g. record_type=Sync but the id belongs to an ImportSession), or an id that doesn't exist in any of the four tables.","commonSituations":"Hand-crafted requests or stale UI links after a record was deleted or finished; scripts that guess the type string; passing the model's table name or a downcased symbol (\"sync\", \"imports\") instead of the exact class name; an admin bookmarking a cancel URL for a job that has since completed and been removed.","solutions":["Check that record_type is exactly the base class name (Sync, Import, ImportSession, FamilyExport) or the record's actual class name — the UI always sends base_class names","Verify the id exists and belongs to the table you named; look it up in the matching model before issuing the cancel request","If you added a new cancellable type, add its base class to CANCELLABLE_BASE_TYPES in app/controllers/settings/background_jobs_controller.rb:4","Treat the 404 as authoritative: the job either finished, was removed, or was never cancellable — re-render the console page for fresh state"],"exampleFix":"# before (curl/console)\nSettings::BackgroundJobsController # e.g. request with record_type: \"imports\", id: 42\n\n# after: use the exact base class name\n# POST /settings/background_jobs/42/cancel with record_type=Import\n# or in Ruby:\nbase = [Sync, Import, ImportSession, FamilyExport].find { |b| b.name == claimed_type }\nraise ArgumentError, \"bad type\" unless base && base.exists?(42)","handlingStrategy":"validation","validationCode":"# Ruby client, before issuing the cancel request\nALLOWED = %w[Sync Import ImportSession FamilyExport].freeze\n\ndef valid_cancel?(record_type, id)\n  base = ALLOWED.find { |name| name == record_type }\n  return false unless base\n  base.constantize.exists?(id)\nend","typeGuard":"def cancellable_type?(value)\n  %w[Sync Import ImportSession FamilyExport].include?(value.to_s)\nend","tryCatchPattern":"# In a Rails controller that proxies the cancel call\nrescue ActiveRecord::RecordNotFound\n  redirect_to settings_background_jobs_path, alert: \"Job not found or not cancellable\"\nend","preventionTips":["Always send the base class name the UI sends (Sync, Import, ImportSession, FamilyExport)","Look the record up yourself before issuing cancel; treat a completed job as gone","Never guess type strings from table names or symbols","Refresh the background jobs console before acting on stale rows"],"tags":["rails","record-not-found","params-validation","sti","admin-ui"],"backgroundTag":"record-not-found","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}