HeyPuter/puter · error · HttpError
forbidden
forbidden
Error message
Cannot delete a protected app
What it means
Thrown by delete when the target app has `protected === true`. Protected apps are system/built-in apps that must never be removed. Returns HTTP 403 with legacyCode `forbidden`, and fires before the write-access permission check.
Source
Thrown at src/backend/drivers/apps/AppDriver.js:520
async upsert({ uid, id, object, options } = {}) {
const existing = uid || id ? await this.#resolve({ uid, id }) : null;
if (existing) return this.update({ uid: existing.uid, object });
return this.create({ object, options });
}
async delete({ uid, id } = {}) {
const actor = this.#requireActor();
this.#requireUserOrAppActor(actor);
const app = await this.#resolve({ uid, id });
if (!app)
throw new HttpError(404, 'App not found', {
legacyCode: 'not_found',
});
if (app.protected) {
throw new HttpError(403, 'Cannot delete a protected app', {
legacyCode: 'forbidden',
});
}
await this.#checkWriteAccess(app, actor);
await this.appStore.delete(app.id);
this.#emitAppChanged({ app: null, old_app: app, action: 'deleted' });
return { success: true, uid: app.uid };
}
// -- Event emission -----------------------------------------------
//
// Consumers (AppIconService, future cf-file-cache port, billing
// event handlers) key off `app_uid`; the full `app` / `old_app`
// payload lets cache invalidators compute exact origins.
View on GitHub (pinned to 908ec23eda)
Solutions
- Do not delete protected/system apps; they are not removable by design.
- Filter your delete candidates by `app.protected === false` before calling delete.
- If you genuinely need it gone in a self-hosted env, clear the `protected` flag in the data store (operator action), then retry.
Example fix
// before
apps.forEach(a => driver.delete({ uid: a.uid }));
// after
apps
.filter(a => !a.protected)
.forEach(a => driver.delete({ uid: a.uid })); Defensive patterns
Strategy: validation
Validate before calling
// Skip protected apps in bulk delete paths
for (const a of apps) {
if (a.protected) continue;
await driver.delete({ uid: a.uid });
} Type guard
/** @param {object} app @returns {boolean} */
function isDeletable(app) {
return Boolean(app) && app.protected !== true;
} Try / catch
try {
await driver.delete({ uid });
} catch (e) {
if (e.code === 'forbidden' && app?.protected) { /* skip protected */ return; }
throw e;
} Prevention
- Filter delete candidates by `!protected` before calling delete.
- Never write generic 'delete everything' scripts without a protected-app guard.
- Distinguish system/built-in apps from user apps in your UI.
When it happens
Trigger: Calling `driver.delete({ uid })` against a built-in/system app flagged `protected` (e.g. core Puter apps). The check is `if (app.protected)` immediately after the not-found check.
Common situations: Attempted cleanup script that deletes all apps indiscriminately; trying to remove a first-party app during dev/testing; confusion between a user-created app and a system app sharing a similar name.
Related errors
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/83023a0d42709343.
Report an issue: GitHub.