Bug: BillingAccountGitlabInstance last_used_license not updated when instance re-activates under a different BillingAccount

Problem

When a self-managed instance activates with subscription A (under BillingAccount X) and later re-activates with subscription B (under a different BillingAccount Y), the existing BillingAccountGitlabInstance record is not updated. Instead, a new record is created with a random cdot_gitlab_instance_uid, leaving the original record stale — still pointing to the old/cancelled subscription.

This causes the Consumer Resolver to fail with:

Unable to find billing source for the instance <uuid>

because SubscriptionsFinder looks up the BillingAccountGitlabInstance by cdot_gitlab_instance_uid (which matches the original instance_identifier), finds the stale record, and resolves a cancelled Zuora subscription — returning {}.

Root Cause

In AccountGitlabInstance#find_or_create_from_instance_identifier (app/models/concerns/account_gitlab_instance.rb):

def find_or_create_from_instance_identifier(account:, instance_identifier:, last_used_license:)
  instance = create_with(cdot_gitlab_instance_uid: instance_identifier)
    .find_or_initialize_by(**find_or_init_params(account, instance_identifier))
  instance.last_used_license = last_used_license

  begin
    return instance if instance.save

    raise ActiveRecord::RecordNotUnique if instance.errors.details[:cdot_gitlab_instance_uid].any? do |e|
      e[:error] == :taken
    end
  rescue ActiveRecord::RecordNotUnique
    instance.cdot_gitlab_instance_uid = SecureRandom.uuid  # <-- new random UID assigned!
    instance.save
  end

  instance
end

find_or_init_params for BillingAccountGitlabInstance scopes the lookup by billing_account + instance_identifier. When the billing account changes, no existing record is found, so a new one is initialized. Saving fails on the cdot_gitlab_instance_uid uniqueness constraint (the old record still holds the original UID). The rescue path then assigns a random UUID and saves a new record — leaving the old record untouched with a stale last_used_license.

Impact

  • Customers who have re-activated their instance under a new/different subscription (e.g. after a subscription renewal that changed the Zuora account, or after activating a $0 trial subscription and then a paid one) cannot use their credits.
  • Re-activating the activation code does not fix the issue because the stale BillingAccountGitlabInstance record is never updated.
  • The SubscriptionsFinder resolves the old cancelled subscription and returns no billing source.

Steps to Reproduce

  1. Instance activates with subscription A under BillingAccount X → BillingAccountGitlabInstance created with cdot_gitlab_instance_uid = instance_identifier.
  2. Instance re-activates with subscription B under BillingAccount Y (different account).
  3. A new BillingAccountGitlabInstance is created with a random cdot_gitlab_instance_uid.
  4. The old record (BillingAccount X, cdot_gitlab_instance_uid = instance_identifier) remains with last_used_license pointing to the cancelled subscription A.
  5. Consumer Resolver looks up by cdot_gitlab_instance_uid = instance_identifier, finds the stale record, fails to resolve an active subscription → credits unavailable.

Proposed Fix

When saving a new BillingAccountGitlabInstance fails due to a cdot_gitlab_instance_uid uniqueness conflict, instead of creating a new record with a random UID, find the existing record by cdot_gitlab_instance_uid and update its last_used_license (and billing_account if changed).

References

  • Related (but different) issue: #14626 (instance_id disambiguation for shared subscriptions)
  • app/models/concerns/account_gitlab_instance.rb
  • app/finders/billing/usage/self_managed/subscriptions_finder.rb
  • app/models/consumer/resolver.rb

Support Priority Score: (1, 0, 0, 2, 3, 3, 3, 2, 3, 2, 3) => 22

Edited Feb 27, 2026 by Firdaws Farukh
Assignee Loading
Time tracking Loading