antiwork/gumroad · error · Link::LinkInvalid
Could not disconnect the #{integration.name.tr("_", " ")} in
Error message
Could not disconnect the #{integration.name.tr("_", " ")} integration, please try again. What it means
Product::SaveIntegrationsService diffs the product's active integrations against the submitted ones; for each removal whose connection is not reused on another of the seller's products, it calls integration.disconnect! — a live, irreversible third-party call (Discord bot removal, Google OAuth token revocation). If disconnect! returns falsy, the error is added to product.errors and Link::LinkInvalid is raised, so the product save fails rather than half-commit.
Source
Thrown at app/services/product/save_integrations_service.rb:71
other_products_by_user = Link.where(user_id: product.user_id).alive.where.not(id: product.id).pluck(:id)
integrations_on_other_products = Integration.joins(:product_integration).where("product_integration.product_id" => other_products_by_user, "product_integration.deleted_at" => nil)
deleted_integrations = integrations_to_delete(enabled_integrations)
deletion_successful = product.live_product_integrations.where(integration: deleted_integrations).reduce(true) do |success, product_integration|
integration = product_integration.integration
same_connection_exists = integrations_on_other_products.find { |other_integration| integration.same_connection?(other_integration) }
disconnection_successful = same_connection_exists ? true : integration.disconnect!
if disconnection_successful
product_integration.mark_deleted
success
else
product.errors.add(:base, "Could not disconnect the #{integration.name.tr("_", " ")} integration, please try again.")
false
end
end
raise Link::LinkInvalid unless deletion_successful
product.active_integrations << enabled_integrations - product.active_integrations
end
private
def contract_enforced?
contract.present? && contract.enforced?
end
# True when this request expressed no intent about integrations at all:
# the collection wasn't submitted (absent and {} read the same, Rule 1)
# and no explicit deletion targets it. `submitted?` is presence-based, so
# an empty hash — the shape strong parameters produces when every entry
# is malformed — also counts as "not submitted".
def no_integrations_intent?
!contract.submitted?(:integrations) &&
contract.deleted_ids(:integrations).empty? &&
!contract.cleared?(:integrations)View on GitHub (pinned to afeacbd394)
Solutions
- Retry the save — transient provider errors usually clear on the next attempt
- If it keeps failing, reconnect/re-authenticate the integration first, then remove it
- Check the provider's status page (Discord/Google/etc.) and the app logs for the underlying disconnect! failure
- Removing the same connection from a product while another product still uses it skips disconnect! entirely — so as a workaround, the connection on other products is unaffected either way
Example fix
# before
Product::SaveIntegrationsService.perform(product, { discord: nil })
# provider 500 -> product.errors[:base], Link::LinkInvalid
# after
begin
Product::SaveIntegrationsService.perform(product, { discord: nil })
rescue Link::LinkInvalid
retry if (attempts += 1) < 3 # transient provider failure
raise
end Defensive patterns
Strategy: retry
Validate before calling
# Best available pre-check: verify the connection is still live before removing integration = product.find_integration_by_name(name) raise 'integration not connected' if integration.nil? # disconnect! hits the provider; a dead/expired token is the usual persistent failure
Try / catch
begin
attempts = (attempts || 0) + 1
Product::SaveIntegrationsService.perform(product, integration_params)
rescue Link::LinkInvalid
if product.errors[:base].any? { _1.start_with?('Could not disconnect') } && attempts < 3
sleep(2**attempts) && retry # transient provider failure
else
raise # persistent: re-authenticate the integration, then remove again
end
end Prevention
- disconnect! is a live third-party call with no undo — only include integration removals in saves that truly intend it
- Re-authenticate long-expired integrations before removing them; revoking dead tokens is the classic persistent failure
- Remember the same connection used by another product skips disconnect! entirely, so cross-product reuse dodges the provider call
- Check the provider's status page before retrying during an incident
When it happens
Trigger: Removing a Discord/Google/Circle integration from a product when the provider API call inside disconnect! errors or returns false: expired or already-revoked OAuth tokens, provider outage, rate limiting, or network failure to the third party.
Common situations: Revoking an integration whose credentials expired long ago (the revoke call itself fails); provider-side incidents; flaky egress networking.
Related errors
- Message is required
- That file is too large. Images can be up to #{MAX_IMAGE_BYTE
- We couldn't download that file, please check the URL and try
- You can attach up to #{DisputeEvidence::MAX_CUSTOMER_COMMUNI
- One of the uploaded files is not a JPG, PNG, or PDF.
AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21).
Data as JSON: /api/errors/386b7c9a156d4012.
Report an issue: GitHub.