we-promise/sure · warning

<%= class_name %>Item Unlinker: failed to fully unlink provi

Error message

<%= class_name %>Item Unlinker: failed to fully unlink provider account ##{provider_account.id} (links=#{link_ids.inspect}): #{e.class} - #{e.message}

What it means

Generated unlinking concern processes each provider account of an item being unlinked: inside a transaction it nils out holdings referencing the account_provider links (update_all) and destroys each link. On StandardError the transaction rolls back for that account, the warning is logged with the account id and link ids, result[:error] records the message, and processing continues with remaining accounts — so an unlink operation can end partially complete for multi-account items.

Source

Thrown at lib/generators/provider/family/templates/unlinking_concern.rb.tt:39

      }
      results << result

      next if dry_run

      begin
        ActiveRecord::Base.transaction do
          # Detach holdings for any provider links found
          if link_ids.any?
            Holding.where(account_provider_id: link_ids).update_all(account_provider_id: nil)
          end

          # Destroy all provider links
          links.each do |ap|
            ap.destroy!
          end
        end
      rescue StandardError => e
        Rails.logger.warn(
          "<%= class_name %>Item Unlinker: failed to fully unlink provider account ##{provider_account.id} (links=#{link_ids.inspect}): #{e.class} - #{e.message}"
        )
        # Record error for observability; continue with other accounts
        result[:error] = e.message
      end
    end

    results
  end
end

View on GitHub (pinned to e69894adb9)

Solutions

  1. Read the account id and e.class/e.message from the log, then check that account's holdings and account_provider rows — a rolled-back transaction leaves both untouched
  2. Retry the unlink for the affected provider account once the concurrent sync job finishes (per-account processing makes partial retries safe)
  3. Audit account_provider destroy callbacks for raises; make them non-blocking or ensure they only fire on intentional destroys
  4. If lock contention recurs, shrink the transaction or add WITH LOCK timeouts / retry logic around the unlink
Defensive patterns

Strategy: try-catch

Validate before calling

# Before unlinking, confirm no concurrent sync is running against these holdings
links.each do |ap|
  next unless ap.persisted? && ap.account.present? # skip already-removed links
end
Holding.where(account_provider_id: link_ids).update_all(account_provider_id: nil)

Try / catch

rescue ActiveRecord::Deadlocked, ActiveRecord::LockWaitTimeout => e
  # transient contention with a running sync - safe to retry per account later
  result[:error] = "lock-timeout:#{e.class}"
rescue StandardError => e
  Rails.logger.warn("... failed to fully unlink provider account ##{provider_account.id} (links=#{link_ids.inspect}): #{e.class} - #{e.message}")
  result[:error] = e.message
end

Prevention

When it happens

Trigger: Holding.update_all hitting a lock/statement timeout while a sync job concurrently touches holdings; ap.destroy! raising ActiveRecord::RecordNotDestroyed when account_provider destroy callbacks veto deletion; DB constraint or serialization failures inside the transaction (unlinking_concern.rb.tt:39 region).

Common situations: User unlinks an item while a background sync is running against the same holdings; association callbacks (on account_provider) raising after customization; long-running transactions causing deadlocks under load.

Related errors


AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21). Data as JSON: /api/errors/e40849d897bdac91. Report an issue: GitHub.