{"record":{"id":"4abb997e34c4d247","repo":"HeyPuter/puter","slug":"not-found","errorCode":"not_found","errorMessage":"App not found","messagePattern":"App not found","errorType":"http","errorClass":"HttpError","httpStatus":404,"severity":"error","filePath":"src/backend/controllers/apps/AppController.js","lineNumber":243,"sourceCode":"                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',\n                    });\n\n                // Validation and authorization are settled by this point, so\n                // the caller learns the outcome now and the stats write lands\n                // on its own. See `#recordAppOpen`.\n                this.#recordAppOpen(app_uid, req.actor.user.id);\n\n                res.json({});\n            },\n        );\n\n        // GET /apps/:name — returns the app(s) by name.\n        // Supports pipe-separated names for batch lookup: /apps/foo|bar|baz\n        router.get(\n            '/apps/:name',\n            {\n                subdomain: 'api',","sourceCodeStart":225,"sourceCodeEnd":261,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/src/backend/controllers/apps/AppController.js#L225-L261","documentation":"Returned by POST /rao after the authorization checks pass but appStore.getByUid(app_uid) resolves to null — the app uid supplied (from the body or the actor's bound app) does not correspond to any stored app. It indicates the credential is valid but points at a nonexistent app record.","triggerScenarios":"Posting an app_uid that was deleted, never existed, or is malformed-but-nonempty; using an app token whose bound app.uid was removed from the store after the token was minted.","commonSituations":"Stale token from a deleted app; typo in a hardcoded app_uid; test environment where the app row was never seeded; race where the app is removed between token issuance and the open report.","solutions":["Verify the app_uid exists (GET /apps/:name or a lookup) before reporting opens.","If using an app actor, ensure the token was minted for a currently-registered app; re-issue the token if the app was re-created with a new uid.","Check for typos or copy errors in the app_uid value passed in the body."],"exampleFix":"// before\nawait fetch('/rao', { method:'POST', body:JSON.stringify({app_uid: 'app-does-not-exist'}) });\n\n// after: confirm the uid resolves first\nconst app = await (await fetch(`/apps/${name}`)).json();\nawait fetch('/rao', { method:'POST', body:JSON.stringify({app_uid: app.uid}) });","handlingStrategy":"validation","validationCode":"const exists = await appStore.getByUid(app_uid);\nif (!exists) throw new Error('Refusing to report open for unknown app');","typeGuard":"/** @returns {boolean} */\nfunction isValidAppUid(uid) {\n  return typeof uid === 'string' && uid.length > 0 && uid.startsWith('app-');\n}","tryCatchPattern":"try { await postRao(app_uid); }\ncatch (e) { if (e.code === 'not_found') { /* re-resolve uid or drop the report */ } else throw e; }","preventionTips":["Resolve the app uid from a trusted source (the app record) rather than hardcoding.","Treat a 404 as non-fatal for analytics — drop the report rather than retrying forever."],"tags":["apps","not-found","analytics","validation"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}