antiwork/gumroad · error · Link::LinkInvalid
Cannot remove files still referenced in rich content: #
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.
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: #
- Cannot switch to per-variant content: the product has no…
- Cannot switch to shared content: both product-level and…
- Cannot switch to shared content: multiple variants have…
- Cannot update product-level rich content while in…
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/f42b785fa843c4c3.
Report an issue: GitHub.
Appendix: 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)