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
- Remove the file embeds from rich_content first (or in the same request), then delete the files — the message names the conflicting ids.
- When building clients, before removing a file, scan current rich content for embeds of that id and rewrite those nodes in the same payload.
- If you intend to keep the embeds, keep the files alive (don't send them in the removal set).
- 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
- Scan current rich content for embeds before removing any file; rewrite those nodes in the same payload.
- Have the file-delete UI offer to strip the embeds too.
- Remember variant content embeds count as well as product-level ones.
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
- File embeds reference files not belonging to this product: #
- File(s) #{missing_ids.join(', ')} no longer exist; they may
- Cannot update product-level rich content while in per-varian
- Cannot switch to shared content: both product-level and vari
- Cannot switch to shared content: multiple variants have dist
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/f42b785fa843c4c3.
Report an issue: GitHub.