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

  1. Retry the save — transient provider errors usually clear on the next attempt
  2. If it keeps failing, reconnect/re-authenticate the integration first, then remove it
  3. Check the provider's status page (Discord/Google/etc.) and the app logs for the underlying disconnect! failure
  4. 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

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


AI-assisted analysis of antiwork/gumroad@afeacbd394 (2026-08-21). Data as JSON: /api/errors/386b7c9a156d4012. Report an issue: GitHub.