antiwork/gumroad · error · Link::LinkInvalid

Cannot remove files still referenced in rich content: #{conf

Error message

Cannot remove files still referenced in rich content: #{conflicting.join(", ")}. Remove the file embeds from rich content first, or send both changes together.

What it means

Link::LinkInvalid raised by validate_file_embed_conflicts! in Api::V2::LinksController#update: the request removes files (ids in removing_ids) that are still embedded in product-level or variant-level rich content (embedded_product_file_ids_in_order, obfuscated for comparison). Deleting an embedded file would leave dead embeds in the content, so the API requires you to remove the embeds first or send both changes atomically.

Source

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

      return if removing_ids.empty?

      product_embed_ids = if @normalized_rich_content
        extract_file_embed_ids_from_params(@normalized_rich_content)
      else
        @product.alive_rich_contents.flat_map(&:embedded_product_file_ids_in_order).map { ObfuscateIds.encrypt(_1) }
      end

      variant_embed_ids = if skip_variant_embeds
        []
      else
        @product.alive_variants.flat_map { |v| v.alive_rich_contents.flat_map(&:embedded_product_file_ids_in_order) }.map { ObfuscateIds.encrypt(_1) }
      end

      all_embed_ids = (product_embed_ids + variant_embed_ids).uniq
      conflicting = removing_ids & all_embed_ids
      return if conflicting.empty?

      raise Link::LinkInvalid, "Cannot remove files still referenced in rich content: #{conflicting.join(", ")}. Remove the file embeds from rich content first, or send both changes together."
    end

    def extract_file_embed_ids_from_params(rich_content_pages)
      return [] if rich_content_pages.blank?

      rich_content_pages.flat_map do |page|
        content = unwrap_description_content(page[:description])
        next [] if content.blank?
        extract_file_ids_from_nodes(content)
      end.compact.uniq
    end

    def extract_file_ids_from_nodes(nodes)
      nodes.flat_map do |node|
        ids = []
        if node[:type] == RichContent::FILE_EMBED_NODE_TYPE
          id = node.dig(:attrs, :id)
          ids << id if id.present?

View on GitHub (pinned to afeacbd394)

Solutions

  1. Remove the file embeds from rich_content first (or in the same request), then delete the files — the message names the conflicting ids.
  2. When building clients, before removing a file, scan current rich content for embeds of that id and rewrite those nodes in the same payload.
  3. If you intend to keep the embeds, keep the files alive (don't send them in the removal set).
  4. Note the skip_variant_embeds subtlety: a flag change request relaxes variant checks in one path — revalidate after the write rather than assuming.

Example fix

# before: delete a file that a page still embeds
LinkApi.update(link_id, files: current_files.reject { |f| f[:id] == target_id })
# after: strip embeds from the content in the same request
cleaned_pages = remove_embeds(pages, target_id)
LinkApi.update(link_id, rich_content: cleaned_pages, files: current_files.reject { |f| f[:id] == target_id })
Defensive patterns

Strategy: validation

Validate before calling

embeds = extract_file_embed_ids(rich_content_pages)
conflicts = files_to_remove & embeds
scrub_embeds!(rich_content_pages, conflicts) if conflicts.any? # then submit both together

Type guard

def removing_files_embedded?(removing_ids, product)
  embed_ids = product.alive_rich_contents.flat_map(&:embedded_product_file_ids_in_order)
  (removing_ids.map(&:to_i) & embed_ids).any?
end

Prevention

When it happens

Trigger: PATCH /api/v2/links/:id whose files array drops a file while current rich content (product pages, or variant pages unless skip_variant_embeds) still references that file's id; e.g. deleting a PDF that a content page embeds.

Common situations: Integrations managing the file list without parsing rich content for embeds; sellers deleting files from the file manager while the content still shows them; UI file-delete not offering to also strip the embed.

Related errors


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