antiwork/gumroad · error · Link::LinkInvalid

Cannot switch to shared content: multiple variants have dist

Error message

Cannot switch to shared content: multiple variants have distinct content. Remove variant content first, or send replacement rich_content in the same request.

What it means

Link::LinkInvalid raised by migrate_to_shared_rich_content! when switching to shared content: more than one variant has alive rich contents and they are not identical after canonicalization (canonicalize_rich_contents normalizes for comparison). Shared mode renders one content set for all variants, so distinct variant contents cannot be auto-merged — the API names the conflict and requires deduplication or replacement content.

Source

Thrown at app/controllers/api/v2/links_controller.rb:981

      if @product.has_same_rich_content_for_all_variants?
        migrate_to_shared_rich_content!
      else
        migrate_to_per_variant_rich_content!
      end
    end

    def migrate_to_shared_rich_content!
      variants_with_content = @product.alive_variants.select { |v| v.alive_rich_contents.any? }

      if @product.alive_rich_contents.any? && variants_with_content.any?
        raise Link::LinkInvalid, "Cannot switch to shared content: both product-level and variant-level content exist. Remove one side first, or send replacement rich_content in the same request."
      end

      if variants_with_content.length > 1
        canonical = canonicalize_rich_contents(variants_with_content.first)
        all_identical = variants_with_content.all? { |v| canonicalize_rich_contents(v) == canonical }
        if !all_identical
          raise Link::LinkInvalid, "Cannot switch to shared content: multiple variants have distinct content. Remove variant content first, or send replacement rich_content in the same request."
        end
      end

      source_variant = nil
      if @product.alive_rich_contents.empty? && variants_with_content.any?
        source_variant = variants_with_content.first
        source_variant.alive_rich_contents.sort_by(&:position).each_with_index do |rc, index|
          @product.alive_rich_contents.create!(title: rc.title, description: rc.description_without_stale_dead_cross_product_file_embeds, position: index)
        end
      end

      @product.alive_variants.each do |variant|
        retire_upsells_from_rich_contents!(variant.alive_rich_contents) if variant != source_variant
        variant.alive_rich_contents.each(&:mark_deleted!)
        variant.product_files = []
      end
    end

View on GitHub (pinned to afeacbd394)

Solutions

  1. Pick the winning variant's content: delete the other variants' content, then switch the flag (the remaining single variant migrates up).
  2. Or send rich_content in the same switch request to explicitly define the shared content.
  3. If you believe the contents are equivalent, diff them canonically (embeds ids, page breaks, titles) to find the real difference.
  4. Bulk tooling: only auto-switch products where variants_with_content.length <= 1 or contents canonicalize equal.

Example fix

# before: distinct variant contents + flag flip -> rejected
LinkApi.update(link_id, has_same_rich_content_for_all_variants: true)
# after: choose winner explicitly
variant_b_contents.each(&:delete)
LinkApi.update(link_id, has_same_rich_content_for_all_variants: true) # variant A's content migrates up
Defensive patterns

Strategy: validation

Validate before calling

contents = product.alive_variants.map(&:alive_rich_contents)
canonical = contents.map { |set| canonicalize(set) }.uniq
auto_switch_only_if(contents.length <= 1 || canonical.length == 1)

Type guard

def variant_contents_identical?(product)
  sets = product.alive_variants.map(&:alive_rich_contents).reject(&:empty?)
  return true if sets.length <= 1
  sets.map { |s| canonicalize_rich_contents(s) }.uniq.length == 1
end

Prevention

When it happens

Trigger: PATCH /api/v2/links/:id flipping has_same_rich_content_for_all_variants to true (without replacement rich_content) on a product whose two-plus variants each have content that differs in any canonicalized way (text, embeds, ordering, page count).

Common situations: Sellers who customized content per variant (the whole point of per-variant mode) later merging; identical-looking pages that differ by an embed id or trailing whitespace caught by canonicalization; scripts assuming 'variants have same content' without comparing.

Related errors


AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21). Data as JSON: /api/errors/57894144c89e4e4a. Report an issue: GitHub.