feat: Add option to delete or keep API keys when uninstalling plugin
Description
Summary
This PR automatically deletes plugin credentials when uninstalling a plugin, preventing orphaned API keys from persisting in the database.
Updated Implementation (Nov 29, 2025): Following review feedback from @Yeuoly, this PR has been completely refactored to use automatic forced deletion instead of user selection.
"IMO, whether to delete credentials should not be an option to users, I prefer deleting them forcefully." - @Yeuoly
Problem (Issue #27531)
When users deleted a plugin, associated API keys remained in the database:
- ❌ Credentials silently retained after plugin deletion
- ❌ No cleanup of unused credentials
- ❌ API keys automatically restored on plugin reinstall
- ❌ Users confused about credential persistence
User complaint:
"I deleted the OpenAI plugin, and after reinstalling it, the original API key was automatically restored. I want to clear it."
Solution
Simple automatic deletion:
- ✅ When plugin is uninstalled, all associated credentials are automatically deleted
- ✅ No user interaction required
- ✅ Clean database state after uninstall
- ✅ Plugin reinstall requires fresh API key configuration
Changes
Backend (api/services/plugin/plugin_service.py)
Modified the uninstall() method to:
- Get plugin information before uninstalling
- Query all associated
ProviderCredentialentries (matchingplugin_id/%pattern) - Delete all found credentials
- Proceed with plugin uninstall
@staticmethod def uninstall(tenant_id: str, plugin_installation_id: str) -> bool: # Get plugin info before uninstalling plugins = manager.list_plugins(tenant_id) plugin = next((p for p in plugins if p.installation_id == plugin_installation_id), None)
if plugin:
# Delete all credentials matching plugin_id/%
credentials = db.session.scalars(
select(ProviderCredential).where(
ProviderCredential.tenant_id == tenant_id,
ProviderCredential.provider_name.like(f"{plugin_id}/%"),
)
).all()
for cred in credentials:
db.session.delete(cred)
db.session.commit()
return manager.uninstall(tenant_id, plugin_installation_id)**Key Features:**
- Uses lazy
%logging format (Python best practice) - Graceful error handling - continues uninstall even if credential deletion fails
- Logs credential deletion operations for debugging
Code Comparison
Previous approach: +435 −23 lines (user selection UI, multiple components)
Current approach: +57 −0 lines (automatic deletion, backend only)
Simplification:
- ✅ 87% less code
- ✅ No frontend changes needed
- ✅ Simpler to maintain
- ✅ Follows reviewer recommendation
Testing
Manual Testing:
- ✅ Install plugin with API key configuration
- ✅ Uninstall plugin → credentials automatically deleted
- ✅ Reinstall plugin → requires new API key (old one gone)
- ✅ Database queries confirm no orphaned credentials
Tested with Docker deployment:
- All services running correctly
- Plugin uninstall flow working as expected
- No database orphaned records
Database Impact
Affected Tables:
provider_credential- Provider-level credentials deleted via pattern matchplugin_id/%
Query Pattern: DELETE FROM provider_credential WHERE tenant_id = ? AND provider_name LIKE 'plugin_id/%'---
How This Solves Issue #27531
Before:
- User deletes OpenAI plugin
- API key remains in database
- Plugin reinstall → old API key restored
- ❌ User frustrated, wants to clear it
After:
- User deletes OpenAI plugin
- API key automatically deleted from database
- Plugin reinstall → requires new API key
- ✅ Clean state, issue resolved
Related Issue
Fixes #27531
Checklist
- [x] Code follows project coding standards
- [x] Self-reviewed the code
- [x] Added necessary comments
- [x] Changes generate no new warnings
- [x] Tested locally with Docker
- [x] CI checks passing
- [x] Addressed all review feedback
Summary of Changes
Hello @UMDKyle, I'm Gemini Code Assist[^1]! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!
This pull request significantly enhances the plugin uninstallation experience by providing users with explicit control over the lifecycle of their API keys and credentials. Previously, these sensitive details would remain in the database even after a plugin was removed, leading to potential security concerns and user confusion upon reinstallation. The new implementation introduces a clear, user-friendly mechanism to either securely delete these credentials or intentionally retain them, directly resolving a critical user feedback and improving data hygiene.
Highlights
- User Control over API Keys: Introduces a user-facing option to either delete or retain associated API keys and credentials when uninstalling a plugin, addressing a long-standing issue where credentials would persist silently.
- Two-Step Deletion Process: Implements a new workflow where, upon initiating plugin uninstallation, the system first checks for associated credentials and then presents the user with clear choices: delete all credentials (default) or keep them for future reinstallation.
- New Backend Service for Credential Management: A dedicated
PluginCredentialServicehas been added to handle the retrieval, specific deletion, and bulk deletion of plugin-related credentials from the database. - Updated API Endpoints and Frontend UI: New API endpoints facilitate credential checking, and existing uninstall endpoints now accept parameters for credential deletion. The frontend UI has been updated with radio buttons and a list display of credentials to enable user choice.
- Comprehensive Test Coverage: The feature is backed by 41 new tests, covering both backend logic (26 tests for services and API controllers) and frontend components (15 tests for service functions, state management, and integration flows).
Using Gemini Code Assist
The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.
Invoking Gemini
You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.
| Feature | Command | Description |
|---|---|---|
| Code Review | /gemini review |
Performs a code review for the current pull request in its current state. |
| Pull Request Summary | /gemini summary |
Provides a summary of the current pull request in its current state. |
| Comment | @gemini-code-assist | Responds in comments when explicitly tagged, both in pull request comments and review comments. |
| Help | /gemini help |
Displays a list of available commands. |
Customization
To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.
Limitations & Feedback
Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with :thumbsup: and :thumbsdown: on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.
You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.
[^1]: Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.
/gemini review
Thanks for the PR, please resolve the CI errors :) And you should use existing checkbox component in the project instead.
IMO, whether to delete credentials should not be an option to users, I prefer deleting them forcefully.
How do you think about it? @laipz8200
IMO, whether to delete credentials should not be an option to users, I prefer deleting them forcefully.
How do you think about it? @laipz8200
Hi, I updated the pull request and now api keys will be deleted forcefully. I think this is better and sovles the problem with smaller change.