antiwork/gumroad · error · Link::LinkInvalid

Cannot switch to shared content: both product-level and vari

Error message

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.

What it means

Link::LinkInvalid raised by migrate_to_shared_rich_content! when the product flips to shared content (has_same_rich_content_for_all_variants -> true) but BOTH product-level alive rich contents and at least one variant with alive rich contents exist. There is no unambiguous single source for the shared content, so the migration refuses instead of picking a winner silently; you must remove one side or send replacement rich_content with the switch.

Source

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

      removed = existing_rich_contents - rich_contents_to_keep
      retire_upsells_from_rich_contents!(removed)
      removed.each(&:mark_deleted!)
    end

    def migrate_rich_content_for_flag_change!
      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

View on GitHub (pinned to afeacbd394)

Solutions

  1. Delete the variant-level content (or the product-level pages) first, then switch the flag.
  2. Or send rich_content in the same request — replacement content wins and the both-sides ambiguity disappears.
  3. For bulk migrations, pre-scan products for the mixed state and reconcile each before flipping flags.
  4. Don't try to dodge it via raw DB updates — the same invariant is what the migration protects.

Example fix

# before: ambiguous switch
LinkApi.update(link_id, has_same_rich_content_for_all_variants: true)
# after: send replacement shared content in the same request
LinkApi.update(link_id, has_same_rich_content_for_all_variants: true, rich_content: new_shared_pages)
Defensive patterns

Strategy: validation

Validate before calling

product = LinkApi.get(link_id)
both_sides = product.rich_contents.any? && product.variants.any? { |v| v.rich_contents.any? }
send_replacement_content! if both_sides # or delete one side first

Type guard

def safe_to_switch_to_shared?(product)
  variants_with_content = product.alive_variants.select { |v| v.alive_rich_contents.any? }
  !(product.alive_rich_contents.any? && variants_with_content.any?)
end

Prevention

When it happens

Trigger: PATCH /api/v2/links/:id with has_same_rich_content_for_all_variants changing to true (and no replacement rich_content) on a product where @product.alive_rich_contents.any? && variants_with_content.any? — legacy mixed-state products hit this most.

Common situations: Products left in a mixed state by older flows or partial migrations; sellers who authored variant content while product-level content also exists; scripts toggling the flag without first reconciling content.

Related errors


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