{"record":{"id":"451b31c7fc21a99d","repo":"HeyPuter/puter","slug":"forbidden","errorCode":"forbidden","errorMessage":"Access tokens cannot report app opens","messagePattern":"Access tokens cannot report app opens","errorType":"http","errorClass":"HttpError","httpStatus":403,"severity":"error","filePath":"src/backend/controllers/apps/AppController.js","lineNumber":226,"sourceCode":"                const actor = req.actor;\n                const bodyAppUid = req.body?.app_uid;\n                const actorAppUid = actor?.app?.uid;\n                const app_uid =\n                    typeof bodyAppUid === 'string' && bodyAppUid.length > 0\n                        ? bodyAppUid\n                        : actorAppUid;\n                if (!app_uid || typeof app_uid !== 'string') {\n                    throw new HttpError(400, 'Missing or invalid `app_uid`', {\n                        legacyCode: 'bad_request',\n                    });\n                }\n\n                // Access tokens (and any other non-user/non-app identity,\n                // e.g. asset tokens) are not allowed to report opens —\n                // they're shared / scoped credentials and shouldn't drive\n                // analytics counters.\n                if (isAccessTokenActor(actor)) {\n                    throw new HttpError(\n                        403,\n                        'Access tokens cannot report app opens',\n                        { legacyCode: 'forbidden' },\n                    );\n                }\n\n                if (isAppActor(actor) && app_uid !== actorAppUid) {\n                    throw new HttpError(\n                        403,\n                        'App actors can only report opens for their own app',\n                        { legacyCode: 'forbidden' },\n                    );\n                }\n\n                const app = await this.appStore.getByUid(app_uid);\n                if (!app)\n                    throw new HttpError(404, 'App not found', {\n                        legacyCode: 'not_found',","sourceCodeStart":208,"sourceCodeEnd":244,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/apps/AppController.js#L208-L244","documentation":"AppDriver.update resolves { uid, id } against the store; a null result throws 404 not_found. This runs after the object-shape check but before #checkWriteAccess, so — like read — it does not leak the existence of apps the caller cannot edit. Same lookup path as read(), just on the update branch.","triggerScenarios":"Calling puter.apps.update(uid, patch) for an app that does not exist: deleted between the get() and the update(), typo in the uid, or a uid from a different environment.","commonSituations":"Optimistic UI editing a row another user/process deleted; cross-environment uid drift; id/uid mix-up; long-lived edit form whose target was removed.","solutions":["Handle 404 by refreshing the app list and discarding the stale edit form.","Re-fetch the app immediately before editing if the form was open a long time.","Validate the uid format before calling to fail fast on garbage.","Confirm the uid came from the same environment (dev/prod) as the API target."],"exampleFix":"// before\nawait puter.apps.update(staleUid, { name: 'new' });\n\n// after — handle not-found gracefully\ntry { await puter.apps.update(storedUid, { name: 'new' }); }\ncatch (e) {\n  if (e?.code === 'not_found') { refreshAppList(); notify('This app no longer exists.'); return; }\n  throw e;\n}","handlingStrategy":"try-catch","validationCode":"function looksLikeUid(v) { return typeof v === 'string' && /^[A-Za-z0-9_-]{8,}$/.test(v); }\nif (!looksLikeUid(storedUid)) { refreshAppList(); return; }","typeGuard":null,"tryCatchPattern":"try { await puter.apps.update(storedUid, patch); }\ncatch (e) {\n  if (e?.code === 'not_found') { refreshAppList(); notify('This app no longer exists.'); return; }\n  throw e;\n}","preventionTips":["Re-fetch the app before opening a long-lived edit form to catch deletions.","Handle 404 by discarding the stale form, not by retrying the same uid.","Keep uids environment-scoped to avoid dev/prod drift."],"tags":["apps","not-found","update","lookup"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}