we-promise/sure · warning · ActiveRecord::RecordNotFound

Unknown record type

Error message

Unknown record type

What it means

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.

Source

Thrown at app/controllers/settings/background_jobs_controller.rb:60

    end
  end

  private
    # Resolves record_type without reflecting on request input: rather than
    # turning the param into a constant, look the id up in each cancellable
    # base table and require the claimed type to match the found record's
    # class (or its base class). STI subclass names are still accepted —
    # the UI sends base_class names, but a direct request naming e.g.
    # TransactionImport shouldn't 404.
    def find_record!
      claimed_type = params[:record_type].to_s

      CANCELLABLE_BASE_TYPES.each do |base|
        record = base.find_by(id: params[:id])
        return record if record && [ record.class.name, base.name ].include?(claimed_type)
      end

      raise ActiveRecord::RecordNotFound, "Unknown record type"
    end

    # User-facing: surfaces as the failed operation's error in the family UI.
    def cancelled_error_message
      t("settings.background_jobs.cancel.cancelled_error")
    end

    def cancellable_status?(record)
      case record
      when Sync then record.in_progress?
      when Import then record.importing? || record.reverting?
      when ImportSession then record.importing?
      when FamilyExport then record.pending? || record.processing?
      end
    end

    def apply_cancel!(record)
      case record

View on GitHub (pinned to e69894adb9)

Solutions

  1. 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
  2. Verify the id exists and belongs to the table you named; look it up in the matching model before issuing the cancel request
  3. If you added a new cancellable type, add its base class to CANCELLABLE_BASE_TYPES in app/controllers/settings/background_jobs_controller.rb:4
  4. Treat the 404 as authoritative: the job either finished, was removed, or was never cancellable — re-render the console page for fresh state

Example fix

# before (curl/console)
Settings::BackgroundJobsController # e.g. request with record_type: "imports", id: 42

# after: use the exact base class name
# POST /settings/background_jobs/42/cancel with record_type=Import
# or in Ruby:
base = [Sync, Import, ImportSession, FamilyExport].find { |b| b.name == claimed_type }
raise ArgumentError, "bad type" unless base && base.exists?(42)
Defensive patterns

Strategy: validation

Validate before calling

# Ruby client, before issuing the cancel request
ALLOWED = %w[Sync Import ImportSession FamilyExport].freeze

def valid_cancel?(record_type, id)
  base = ALLOWED.find { |name| name == record_type }
  return false unless base
  base.constantize.exists?(id)
end

Type guard

def cancellable_type?(value)
  %w[Sync Import ImportSession FamilyExport].include?(value.to_s)
end

Try / catch

# In a Rails controller that proxies the cancel call
rescue ActiveRecord::RecordNotFound
  redirect_to settings_background_jobs_path, alert: "Job not found or not cancellable"
end

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/14cca04d343ea218. Report an issue: GitHub.