we-promise/sure · warning · ActiveRecord::RecordNotDestroyed
Cannot destroy demo monitoring API key
Error message
Cannot destroy demo monitoring API key
What it means
ApiKey#delete overrides the model's delete to raise ActiveRecord::RecordNotDestroyed("Cannot destroy demo monitoring API key") when the record is the seeded demo monitoring key (display_key == DEMO_MONITORING_KEY). destroy goes through delete, so both normal and skip-callbacks destruction paths are blocked. Like revoke!, this protects the demo family's external monitoring credential from being removed piecemeal — the demo teardown (Demo::DataCleaner) is the only sanctioned remover, and it uses delete_all to bypass exactly this guard deliberately.
Source
Thrown at app/models/api_key.rb:72
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
display_key
end
# Temporarily store the plain key for creation flow
attr_accessor :keyView on GitHub (pinned to e69894adb9)
Solutions
- Use the sanctioned teardown: Demo::DataCleaner removes demo records and delete_all's the key, bypassing the guard on purpose
- Exclude the key from bulk deletion: ApiKey.visible.each(&:destroy) or next if k.demo_monitoring_key?
- When deleting the demo admin user, run demo cleanup first so the cascade never reaches the protected key
- In tests, never use DEMO_MONITORING_KEY as a factory value; generate random keys
Example fix
# before user.api_keys.each(&:destroy) # => ActiveRecord::RecordNotDestroyed: Cannot destroy demo monitoring API key # after user.api_keys.visible.each(&:destroy) # guard never trips # full demo teardown instead of piecemeal destroy: # Demo::DataCleaner.new.run (uses delete_all for the reserved key)
Defensive patterns
Strategy: validation
Validate before calling
# In cleanup code
ApiKey.visible.each(&:destroy) # skips demo key
# or guard explicitly
keys.each { |k| k.destroy unless k.demo_monitoring_key? } Type guard
def destructible_api_key?(key) !key.demo_monitoring_key? end
Try / catch
rescue ActiveRecord::RecordNotDestroyed => e
if e.message.include?("Cannot destroy demo monitoring API key")
next # reserved key: only Demo::DataCleaner may remove it
else
raise
end
end Prevention
- Exclude the demo key before bulk destroys (visible scope does it for you)
- Delete the demo admin user only via demo cleanup, so cascades never hit the guard
- In tests, generate random key values, never the reserved constant
- Know that both destroy and delete are guarded — there is no in-band bypass
When it happens
Trigger: Running api_key.destroy or api_key.delete on the demo monitoring key from console/script; a generic 'purge old API keys' cleanup task iterating all records; dependent => :destroy on a user being deleted who holds the demo key (Demo::Generator attaches it to the demo admin user) — destroying that user cascades into this raise; factories/tests that accidentally instantiate a key with the demo display_key value.
Common situations: Deleting old/unused keys during an audit and catching the demo one; tearing down a demo admin user without running the demo cleaner; test suites with loose factories producing the reserved display_key; copying production data to staging and cleaning it with naive destroy loops.
Related errors
- Cannot revoke demo monitoring API key
- record_not_found
- validation_failed
- record_not_found
- record_not_found
AI-assisted analysis of we-promise/sure@e69894adb9 (2026-08-21).
Data as JSON: /api/errors/52bd5091b3598199.
Report an issue: GitHub.