langgenius/dify · error · BadRequest
You can't uninstall an app owned by the current tenant
Error message
You can't uninstall an app owned by the current tenant
What it means
HTTP 400 BadRequest raised by DELETE /console/explore/installed-apps/<installed_app_id> when installed_app.app_owner_tenant_id == current_tenant_id. The Explore uninstall flow is only for apps installed FROM another tenant; the owning tenant cannot 'uninstall' its own app here.
Source
Thrown at api/controllers/console/explore/installed_app.py:275
if current_user.current_tenant is None:
raise ValueError("current_user.current_tenant must not be None")
current_user.role = TenantService.get_user_role(current_user, current_user.current_tenant, session=db.session())
return dump_response(
InstalledAppResponse,
_installed_app_response_data(
installed_app,
app_model,
current_tenant_id=current_tenant_id,
current_user=current_user,
),
)
@console_ns.response(204, "App uninstalled successfully")
@with_current_tenant_id
def delete(self, current_tenant_id: str, installed_app: InstalledApp):
if installed_app.app_owner_tenant_id == current_tenant_id:
raise BadRequest("You can't uninstall an app owned by the current tenant")
db.session.delete(installed_app)
db.session.commit()
return "", 204
@console_ns.response(200, "Success", console_ns.models[SimpleResultMessageResponse.__name__])
@console_ns.expect(console_ns.models[InstalledAppUpdatePayload.__name__])
@model_validate(InstalledAppUpdatePayload)
def patch(self, req_data: InstalledAppUpdatePayload, installed_app: InstalledApp):
commit_args = False
if req_data.is_pinned is not None:
installed_app.is_pinned = req_data.is_pinned
commit_args = True
if commit_args:
db.session.commit()
View on GitHub (pinned to ef8544b173)
Solutions
- Do not show the Uninstall action when current_tenant_id === app.app_owner_tenant_id (use the uninstallable flag returned by GET installed-apps).
- For owned apps, use the proper app-deletion endpoint under /console/apps, not the Explore uninstall endpoint.
- Filter the installed-apps list client-side to only offer uninstall on rows where uninstallable === false (per response semantics) — confirm against the actual field meaning.
- If the ownership mapping is wrong, verify app_owner_tenant_id was set correctly at install time.
Example fix
// before: uninstall button shown for every installed app
<button onClick={() => del(`/installed-apps/${a.id}`)}>Uninstall</button>
// after: only show for apps owned by another tenant
{!a.uninstallable && a.app_owner_tenant_id !== currentTenantId && (
<button onClick={() => del(`/installed-apps/${a.id}`)}>Uninstall</button>
)} Defensive patterns
Strategy: validation
Validate before calling
// Only call uninstall when the current tenant is NOT the owner
const isOwnable = installedApp.app_owner_tenant_id === currentTenantId;
if (isOwnable) {
throw new Error('Use the app-deletion flow for apps your tenant owns');
} Type guard
function isUninstallable(installedApp, currentTenantId) {
return installedApp.app_owner_tenant_id !== currentTenantId;
} Try / catch
try {
await del(`/console/explore/installed-apps/${id}`);
} catch (e) {
if (e.status === 400 && /owned by the current tenant/.test(e.message)) {
hideUninstallButton(id); // not uninstallable via Explore
} else throw e;
} Prevention
- Drive Uninstall visibility from the response's uninstallable/ownership fields.
- Keep owned-app management on /console/apps, not the Explore flow.
- Refresh the ownership flag after a tenant switch.
When it happens
Trigger: DELETE /console/explore/installed-apps/<id> where the current tenant is the original owner of the app. The guard short-circuits before the db.session.delete call.
Common situations: UI shows an 'uninstall' button on an app the current tenant actually owns; confusion between 'my workspace apps' and 'Explore-installed apps'; client reuse of the installed-app tile for owned apps.
Related errors
- You can't install a non-public app
- Recommended app not found
- Installed app not found
- App entity not found
- app_unavailable
AI-assisted analysis of langgenius/dify@ef8544b173 (2026-08-12).
Data as JSON: /api/errors/518b791442b299dc.
Report an issue: GitHub.