{"record":{"id":"83023a0d42709343","repo":"HeyPuter/puter","slug":"forbidden-83023a","errorCode":"forbidden","errorMessage":"Cannot delete a protected app","messagePattern":"Cannot delete a protected app","errorType":"http","errorClass":"HttpError","httpStatus":403,"severity":"error","filePath":"src/backend/drivers/apps/AppDriver.js","lineNumber":520,"sourceCode":"\n    async upsert({ uid, id, object, options } = {}) {\n        const existing = uid || id ? await this.#resolve({ uid, id }) : null;\n        if (existing) return this.update({ uid: existing.uid, object });\n        return this.create({ object, options });\n    }\n\n    async delete({ uid, id } = {}) {\n        const actor = this.#requireActor();\n        this.#requireUserOrAppActor(actor);\n\n        const app = await this.#resolve({ uid, id });\n        if (!app)\n            throw new HttpError(404, 'App not found', {\n                legacyCode: 'not_found',\n            });\n\n        if (app.protected) {\n            throw new HttpError(403, 'Cannot delete a protected app', {\n                legacyCode: 'forbidden',\n            });\n        }\n\n        await this.#checkWriteAccess(app, actor);\n        await this.appStore.delete(app.id);\n\n        this.#emitAppChanged({ app: null, old_app: app, action: 'deleted' });\n\n        return { success: true, uid: app.uid };\n    }\n\n    // -- Event emission -----------------------------------------------\n    //\n    // Consumers (AppIconService, future cf-file-cache port, billing\n    // event handlers) key off `app_uid`; the full `app` / `old_app`\n    // payload lets cache invalidators compute exact origins.\n","sourceCodeStart":502,"sourceCodeEnd":538,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/drivers/apps/AppDriver.js#L502-L538","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\napps.forEach(a => driver.delete({ uid: a.uid }));\n\n// after\napps\n  .filter(a => !a.protected)\n  .forEach(a => driver.delete({ uid: a.uid }));","handlingStrategy":"validation","validationCode":"// Skip protected apps in bulk delete paths\nfor (const a of apps) {\n  if (a.protected) continue;\n  await driver.delete({ uid: a.uid });\n}","typeGuard":"/** @param {object} app @returns {boolean} */\nfunction isDeletable(app) {\n  return Boolean(app) && app.protected !== true;\n}","tryCatchPattern":"try {\n  await driver.delete({ uid });\n} catch (e) {\n  if (e.code === 'forbidden' && app?.protected) { /* skip protected */ return; }\n  throw e;\n}","preventionTips":["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."],"tags":["apps","forbidden","protected","delete","driver"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}