we-promise/sure · warning · BrexItem::AccountFlow::AccountAlreadyLinkedError

BrexItem::AccountFlow::AccountAlreadyLinkedError

Error message

BrexItem::AccountFlow::AccountAlreadyLinkedError

What it means

Raised by BrexItem::AccountFlow#link_existing_account! as AccountAlreadyLinkedError when the freshly upserted BrexAccount record already has an account_provider — i.e. that Brex account is already linked to a Maybe account. The raise happens inside ActiveRecord::Base.transaction, so the new AccountProvider creation rolls back and no partial link survives.

Source

Thrown at app/models/brex_item/account_flow.rb:215

    LinkAccountsResult.new(
      created_accounts: created_accounts,
      already_linked_names: already_linked_names,
      invalid_account_ids: invalid_account_ids
    )
  end

  def link_existing_account!(account:, brex_account_id:)
    account_data = indexed_accounts[brex_account_id.to_s]
    raise AccountNotFoundError unless account_data

    account_name = BrexAccount.name_for(account_data)
    raise InvalidAccountNameError if account_name.blank?

    brex_account = nil

    ActiveRecord::Base.transaction do
      brex_account = upsert_brex_account!(brex_account_id, account_data)
      raise AccountAlreadyLinkedError if brex_account.account_provider.present?

      AccountProvider.create!(account: account, provider: brex_account)
    end

    brex_item.sync_later

    brex_account
  end

  private

    def selection_error_payload
      if brex_item_id.present?
        return {
          success: false,
          error: "select_connection",
          error_message: I18n.t("brex_items.select_accounts.select_connection"),
          has_accounts: nil

View on GitHub (pinned to e69894adb9)

Solutions

  1. Drive the picker from select_existing_account_result (built on unlinked_available_accounts), which already filters linked accounts, so the option never appears.
  2. Rescue AccountAlreadyLinkedError and show the 'provider account already linked' alert — link_existing_account_result already maps this if you use the public wrapper.
  3. Unlink the existing account first (Unlinking concern) if you genuinely want to re-point it.
  4. Make the submit action idempotent (disable after click, POST-once tokens) to kill double-submit races.

Example fix

# before
flow.link_existing_account!(account: acct, brex_account_id: id)

# after — use the result wrapper that handles all link failures
result = flow.link_existing_account_result(account: acct, brex_account_id: id)
redirect_to accounts_path, result.flash_type => result.message
# or, calling the bang method directly:
begin
  flow.link_existing_account!(account: acct, brex_account_id: id)
rescue BrexItem::AccountFlow::AccountAlreadyLinkedError
  redirect_to accounts_path, alert: "That Brex account is already linked."
end
Defensive patterns

Strategy: validation

Validate before calling

# Build pickers only from accounts not yet linked
result = flow.select_existing_account_result(accountable_type: "CreditCard") # filters via unlinked_available_accounts
return if result.available_accounts.empty?

Try / catch

begin
  flow.link_existing_account!(account: account, brex_account_id: id)
rescue BrexItem::AccountFlow::AccountAlreadyLinkedError
  redirect_to accounts_path, alert: "That Brex account is already linked to another account."
end

Prevention

When it happens

Trigger: link_existing_account! runs after the same Brex account was already linked elsewhere: a stale picker showed it as available (the unfiltered accounts list includes linked ones), two tabs/users race to link it, or a retry resubmits after a first successful link (app/models/brex_item/account_flow.rb:213-215).

Common situations: Double-clicked submit buttons; back-button resubmission; UI list built from all provider accounts instead of unlinked_available_accounts; one Brex account intentionally or accidentally shared between two Maybe accounts.

Related errors


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