we-promise/sure · warning · ActiveRecord::RecordNotDestroyed

Cannot revoke demo monitoring API key

Error message

Cannot revoke demo monitoring API key

What it means

ApiKey#revoke! refuses to revoke the seeded demo monitoring key: if the record's display_key equals the hardcoded DEMO_MONITORING_KEY constant ("demo_monitoring_key_a1b2c3d4…"), it raises ActiveRecord::RecordNotDestroyed instead of setting revoked_at. That key is created by Demo::Generator so the demo family's health can be monitored externally; revoking it would blind the monitor. The same protection exists on delete. It is excluded from the visible scope in the UI, so hitting this usually means direct console/code interaction.

Source

Thrown at app/models/api_key.rb:67

  # Instance methods
  def active?
    !revoked? && !expired?
  end

  def revoked?
    revoked_at.present?
  end

  def expired?
    expires_at.present? && expires_at < Time.current
  end

  def key_matches?(plain_key)
    display_key == plain_key
  end

  def revoke!
    raise ActiveRecord::RecordNotDestroyed, "Cannot revoke demo monitoring API key" if demo_monitoring_key?
    update!(revoked_at: Time.current)
  end

  def delete
    raise ActiveRecord::RecordNotDestroyed, "Cannot destroy demo monitoring API key" if demo_monitoring_key?
    super
  end

  def demo_monitoring_key?
    display_key == DEMO_MONITORING_KEY
  end

  def update_last_used!
    update_column(:last_used_at, Time.current)
  end

  # Get the plain text API key for display (automatically decrypted by Rails)
  def plain_key

View on GitHub (pinned to e69894adb9)

Solutions

  1. Scope revocation through the visible scope: ApiKey.visible.each(&:revoke!) — it already excludes the demo key
  2. If you truly want the demo key gone, disable/remove the demo feature properly (Demo::DataCleaner deletes demo records including this key via delete_all, bypassing the callback guard)
  3. Exclude it explicitly in custom scripts: next if api_key.demo_monitoring_key?
  4. If you no longer run a demo family, run the demo cleanup/teardown task rather than revoking the key in place

Example fix

# before
ApiKey.all.each(&:revoke!)
# => ActiveRecord::RecordNotDestroyed: Cannot revoke demo monitoring API key

# after
ApiKey.visible.each(&:revoke!)            # skips DEMO_MONITORING_KEY by design
# or, when tearing the demo down entirely:
# Demo::DataCleaner (runs ApiKey.where(display_key: ApiKey::DEMO_MONITORING_KEY).delete_all)
Defensive patterns

Strategy: validation

Validate before calling

# In any revocation script
ApiKey.visible.each(&:revoke!)             # scope excludes the demo key
# or explicitly:
keys.reject(&:demo_monitoring_key?).each(&:revoke!)

Type guard

def revocable_api_key?(key)
  !key.demo_monitoring_key?
end

Try / catch

rescue ActiveRecord::RecordNotDestroyed => e
  if e.message.include?("demo monitoring API key")
    skip # protected demo key: intentionally not revocable
  else
    raise
  end
end

Prevention

When it happens

Trigger: Admin console or script calling api_key.revoke! on the demo monitoring key; a bulk 'revoke all keys' rake task that iterates ApiKey.all (the visible scope excludes it, but all does not); cleanup scripts that revoke keys matching a pattern which happens to include the demo key; attempting revoke after the key was recreated by a demo:refresh job (DemoFamilyRefreshJob manages it and expects it to stay live).

Common situations: Self-hosters hardening their instance by revoking every API key found in the DB, unaware one is load-bearing for demo monitoring; rotating all keys via script; migrating away from the demo and trying to shut its key off through the same code path users' keys go through.

Related errors


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