antiwork/gumroad · error · Link::LinkInvalid
Invalid offer code
Error message
Invalid offer code
What it means
links_controller.rb:1937 converts `ActiveRecord::RecordNotFound` from `@product.user.offer_codes.alive.find_by_external_id!` into `Link::LinkInvalid("Invalid offer code")`. The submitted external id matches no alive offer code owned by the product's seller — deleted code, wrong account, wrong environment, or malformed id. The form save aborts with this message.
Source
Thrown at app/controllers/links_controller.rb:1937
if product_permitted_params[:installment_plan].present?
@product.create_installment_plan!(product_permitted_params[:installment_plan])
end
end
def update_default_offer_code
default_offer_code_id = product_permitted_params[:default_offer_code_id]
return @product.default_offer_code = nil if default_offer_code_id.blank?
offer_code = @product.user.offer_codes.alive.find_by_external_id!(default_offer_code_id)
raise Link::LinkInvalid, "Offer code cannot be expired" if offer_code.inactive?
raise Link::LinkInvalid, "Offer code must apply to this product" unless offer_code.applicable?(@product)
@product.default_offer_code = offer_code
rescue ActiveRecord::RecordNotFound
raise Link::LinkInvalid, "Invalid offer code"
end
def toggle_community_chat!(enabled)
return if [Link::NATIVE_TYPE_COFFEE, Link::NATIVE_TYPE_BUNDLE].include?(@product.native_type)
@product.toggle_community_chat!(enabled)
end
def generate_product_details_using_ai
if Rails.env.test?
generate_product_cover_and_thumbnail_using_ai
generate_product_content_using_ai
else
cover_thread = Thread.new { generate_product_cover_and_thumbnail_using_ai }
content_thread = Thread.new { generate_product_content_using_ai }
cover_thread.join
content_thread.joinView on GitHub (pinned to afeacbd394)
Solutions
- Reload the product editor so the offer-code list reflects current codes, then re-select.
- Verify the id belongs to the same seller account and environment.
- Recreate the deleted code if it was removed by mistake and use its new external id.
- Submit a blank `default_offer_code_id` to clear the assignment instead of sending a dead id.
Example fix
# before
put product_path(@product), params: { product: { default_offer_code_id: deleted_code.external_id } }
# after — guard on resolution before the request
code = @product.user.offer_codes.alive.exists?(external_id: submitted_id) ? submitted_id : nil
put product_path(@product), params: { product: { default_offer_code_id: code } } Defensive patterns
Strategy: validation
Validate before calling
code = @product.user.offer_codes.alive.find_by(external_id: submitted_id) raise Link::LinkInvalid, "Invalid offer code" unless code
Type guard
def resolvable_offer_code?(user, external_id) user.offer_codes.alive.exists?(external_id: external_id) end
Try / catch
begin offer_code = @product.user.offer_codes.alive.find_by_external_id!(id) rescue ActiveRecord::RecordNotFound # refresh picker; never blind-retry the same external id end
Prevention
- Rebuild the offer-code picker on each form load; no cached dropdowns.
- Delete-then-default operations should clear defaults atomically.
- Validate ids against the same seller account and environment before submitting.
- Log the submitted id server-side (it is non-sensitive) to diagnose environment skew.
When it happens
Trigger: Product form submits `default_offer_code_id` for a code deleted in another tab/session; id from a different seller account; id truncated or altered in transit; code exists but is outside the `alive` scope.
Common situations: Stale dropdown/hidden field referencing a deleted code; scripts moving ids between environments; concurrent admin sessions deleting codes mid-edit.
Related errors
- Offer code cannot be expired
- Invalid offer code
- Offer code cannot be expired
- Offer code must apply to this product
- Offer code must apply to this product
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/171609eceddd494f.
Report an issue: GitHub.