{"record":{"id":"c878448f3d82ebb8","repo":"we-promise/sure","slug":"cannot-revoke-demo-monitoring-api-key","errorCode":null,"errorMessage":"Cannot revoke demo monitoring API key","messagePattern":"Cannot revoke demo monitoring API key","errorType":"exception","errorClass":"ActiveRecord::RecordNotDestroyed","httpStatus":null,"severity":"warning","filePath":"app/models/api_key.rb","lineNumber":67,"sourceCode":"  # Instance methods\n  def active?\n    !revoked? && !expired?\n  end\n\n  def revoked?\n    revoked_at.present?\n  end\n\n  def expired?\n    expires_at.present? && expires_at < Time.current\n  end\n\n  def key_matches?(plain_key)\n    display_key == plain_key\n  end\n\n  def revoke!\n    raise ActiveRecord::RecordNotDestroyed, \"Cannot revoke demo monitoring API key\" if demo_monitoring_key?\n    update!(revoked_at: Time.current)\n  end\n\n  def delete\n    raise ActiveRecord::RecordNotDestroyed, \"Cannot destroy demo monitoring API key\" if demo_monitoring_key?\n    super\n  end\n\n  def demo_monitoring_key?\n    display_key == DEMO_MONITORING_KEY\n  end\n\n  def update_last_used!\n    update_column(:last_used_at, Time.current)\n  end\n\n  # Get the plain text API key for display (automatically decrypted by Rails)\n  def plain_key","sourceCodeStart":49,"sourceCodeEnd":85,"githubUrl":"https://github.com/we-promise/sure/blob/e69894adb92547273377398c15f45c979cd9416a/app/models/api_key.rb#L49-L85","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Scope revocation through the visible scope: ApiKey.visible.each(&:revoke!) — it already excludes the demo key","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)","Exclude it explicitly in custom scripts: next if api_key.demo_monitoring_key?","If you no longer run a demo family, run the demo cleanup/teardown task rather than revoking the key in place"],"exampleFix":"# before\nApiKey.all.each(&:revoke!)\n# => ActiveRecord::RecordNotDestroyed: Cannot revoke demo monitoring API key\n\n# after\nApiKey.visible.each(&:revoke!)            # skips DEMO_MONITORING_KEY by design\n# or, when tearing the demo down entirely:\n# Demo::DataCleaner (runs ApiKey.where(display_key: ApiKey::DEMO_MONITORING_KEY).delete_all)","handlingStrategy":"validation","validationCode":"# In any revocation script\nApiKey.visible.each(&:revoke!)             # scope excludes the demo key\n# or explicitly:\nkeys.reject(&:demo_monitoring_key?).each(&:revoke!)","typeGuard":"def revocable_api_key?(key)\n  !key.demo_monitoring_key?\nend","tryCatchPattern":"rescue ActiveRecord::RecordNotDestroyed => e\n  if e.message.include?(\"demo monitoring API key\")\n    skip # protected demo key: intentionally not revocable\n  else\n    raise\n  end\nend","preventionTips":["Always iterate ApiKey.visible for user-facing key operations","Check demo_monitoring_key? before revoke/destroy in custom tooling","Use the demo teardown task to remove demo data wholesale","Never reuse the reserved DEMO_MONITORING_KEY value in fixtures"],"tags":["rails","api-keys","protected-record","demo-data","guard-clause"],"backgroundTag":"protected-record-operation","analyzedSha":"e69894adb92547273377398c15f45c979cd9416a","analyzedAt":"2026-08-21T18:22:41.165Z","schemaVersion":2},"datasetVersion":"2026-08-21T23:17:16.201Z"}