antiwork/gumroad · error · Link::LinkInvalid
File embeds reference files not belonging to this product: #
Error message
File embeds reference files not belonging to this product: #{missing_external.join(", ")} What it means
Link::LinkInvalid raised in Api::V2::VariantsController#update right after saving variant rich content: the saved pages embed file ids (embedded_product_file_ids_in_order) whose count exceeds the alive product files actually found scoped to this product (@product.product_files.alive.where(id: file_ids)). The missing ids are files deleted, belonging to another product, or never created — embedding them would render dead file blocks, so the update is aborted inside the transaction and the ids are reported obfuscated (ObfuscateIds.encrypt).
Source
Thrown at app/controllers/api/v2/variants_controller.rb:48
if !@variant.update(permitted_params)
raise ActiveRecord::Rollback
end
if params.key?(:rich_content)
@product.lock!
if @product.has_same_rich_content_for_all_variants?
raise Link::LinkInvalid, "Cannot update variant rich content while the product uses shared content for all variants. Update product-level rich content instead, or set has_same_rich_content_for_all_variants to false first."
end
save_variant_rich_content!(@variant)
file_ids = @variant.alive_rich_contents.flat_map { _1.embedded_product_file_ids_in_order }.uniq
if file_ids.any?
scoped_files = @product.product_files.alive.where(id: file_ids).to_a
if scoped_files.length != file_ids.length
missing = file_ids - scoped_files.map(&:id)
missing_external = missing.map { ObfuscateIds.encrypt(_1) }
raise Link::LinkInvalid, "File embeds reference files not belonging to this product: #{missing_external.join(", ")}"
end
@variant.product_files = scoped_files
else
@variant.product_files = []
end
Product::SavePostPurchaseCustomFieldsService.new(@product).perform
@product.reload
@product.is_licensed = @product.has_embedded_license_key?
@product.is_multiseat_license = false if !@product.is_licensed
@product.content_updated_at = Time.current
@product.save!
@product.generate_product_files_archives!
end
end
View on GitHub (pinned to afeacbd394)
Solutions
- Replace or remove the reported embeds (the message lists them encrypted, matching API-visible ids) so every embedded file is an alive file of this product.
- Upload the missing files to this product first, then reference their ids in the embeds.
- When copying content between products, re-map file embeds to the destination product's files rather than copying ids verbatim.
- Verify id encoding: the API compares raw ids but reports them obfuscated — make sure your client sends the id form the upload/create response gave you.
Example fix
# before: embeds reference another product's file VariantApi.update(link_id, variant_id, rich_content: pages_with_foreign_embed) # after: upload to this product and re-point the embeds upload = FileApi.upload(link_id, io) pages = repoint_embeds(pages_with_foreign_embed, old_id: foreign_id, new_id: upload.id) VariantApi.update(link_id, variant_id, rich_content: pages)
Defensive patterns
Strategy: validation
Validate before calling
file_ids = pages.flat_map { extract_embedded_file_ids(_1) }.uniq
alive = product.product_files.alive.where(id: file_ids).count
Result.invalid("embeds reference missing files") unless alive == file_ids.length Type guard
def all_embeds_owned_by_product?(pages, product)
ids = pages.flat_map { |p| extract_file_ids_from_nodes(p) }.uniq
ids.empty? || product.product_files.alive.where(id: ids).count == ids.length
end Prevention
- Only embed file ids returned by this product's upload/create responses.
- Re-map embeds when copying content between products.
- Re-check embeds against alive files right before submit; stale ids from deleted files fail the guard.
When it happens
Trigger: PUT/PATCH variant with rich_content embedding file ids that are not alive files of the same product — e.g. ids copied from another product's content, ids of files deleted in a concurrent request, or raw external ids the client fabricated/misdecoded.
Common situations: Copy-pasting content blocks between products (embeds carry the source product's file ids); clients sending obfuscated ids where raw ids are expected (or vice versa); files removed after the client composed the content; template imports with hardcoded embeds.
Related errors
- Cannot remove files still referenced in rich content: #{conf
- 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
- Cannot switch to per-variant content: the product has no var
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/fd3f92948705d1fd.
Report an issue: GitHub.