{"record":{"id":"50a934befb3e97ed","repo":"HeyPuter/puter","slug":"app-not-found","errorCode":null,"errorMessage":"App not found","messagePattern":"App not found","errorType":"http","errorClass":"HttpError","httpStatus":404,"severity":"error","filePath":"extensions/appTelemetry.ts","lineNumber":125,"sourceCode":"        Array<{ user: string; user_uuid: string; user_email?: string | null }>\n    > {\n        if (!app_uuid) throw new HttpError(400, 'Missing `app_uuid`');\n\n        const safeLimit = parseIntParam(limit, {\n            key: 'limit',\n            min: 1,\n            max: MAX_LIMIT,\n            fallback: DEFAULT_LIMIT,\n        });\n        const safeOffset = parseIntParam(offset, {\n            key: 'offset',\n            min: 0,\n            max: MAX_OFFSET,\n            fallback: 0,\n        });\n\n        const app = await this.stores.app.getByUid(app_uuid);\n        if (!app) throw new HttpError(404, 'App not found');\n\n        // The `apps-of-user:<uuid>:write` implicator keys on the owner's\n        // UUID, not the numeric id. Look up the owner explicitly — the raw\n        // app row only carries `owner_user_id`. (v1 got the owner for free\n        // because its entity-storage layer eager-joined the owner row.)\n        const ownerId = (app as { owner_user_id?: number }).owner_user_id;\n        if (!ownerId) throw new HttpError(404, 'App owner not found');\n        const owner = await this.stores.user.getById(ownerId);\n        if (!owner?.uuid) throw new HttpError(404, 'App owner not found');\n\n        const actor = Context.get('actor');\n        if (!actor) throw new HttpError(401, 'Authentication required');\n        const ownsApp = await this.services.permission\n            .check(actor as Actor, `apps-of-user:${owner.uuid}:write`)\n            .catch(() => false);\n        if (!ownsApp) throw new HttpError(403, 'Permission denied');\n\n        const appId = (app as { id: number }).id;","sourceCodeStart":107,"sourceCodeEnd":143,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/extensions/appTelemetry.ts#L107-L143","documentation":"Thrown by capSecondsToRemainingCredits when its `actor` parameter is falsy. This helper clamps the requested clip duration to what the actor's remaining credit can buy, so it must read metering.getRemainingUsage(actor). No actor means the call cannot be metered or clamped, so it refuses with 401 rather than silently producing unbilled output.","triggerScenarios":"The provider's generate() flow calls capSecondsToRemainingCredits without threading an actor through (or Context.get('actor') returned undefined at the call site). Typically a wiring bug: a new code path, internal batch job, or test harness that bypassed the auth middleware.","commonSituations":"Refactor that calls the cap helper from a background queue without populating Context; unit test that constructs the provider directly; an admin/internal route registered without the auth RouteOptions gate.","solutions":["Route the call through the normal authenticated controller/middleware so Context.actor is set.","If invoking capSecondsToRemainingCredits directly, pass the actor explicitly from Context.get('actor') and assert it is present first.","In tests, use setupPuterTestEnv() / a stub Context actor rather than calling the helper bare.","Audit the calling endpoint's RouteOptions to confirm the auth gate is enabled."],"exampleFix":"// before — called without actor context\nconst allowed = await capSecondsToRemainingCredits({\n  metering, actor: undefined, perSecondMicroCents, requestedSeconds: durationSeconds,\n});\n\n// after — require actor up front\nconst actor = Context.get('actor');\nif (!actor) throw new HttpError(401, 'Authentication required', { legacyCode: 'unauthorized' });\nconst allowed = await capSecondsToRemainingCredits({\n  metering, actor, perSecondMicroCents, requestedSeconds: durationSeconds,\n});","handlingStrategy":"validation","validationCode":"const actor = Context.get('actor');\nif (!actor) throw new HttpError(401, 'Authentication required', { legacyCode: 'unauthorized' });\nconst allowed = await capSecondsToRemainingCredits({ metering, actor, perSecondMicroCents, requestedSeconds });","typeGuard":"function hasActor(ctx) {\n  const a = ctx.get('actor');\n  return !!a && (typeof a === 'object') && ('id' in a || 'user' in a);\n}","tryCatchPattern":null,"preventionTips":["Always invoke generation through authenticated routes that populate Context.actor.","In tests, use setupPuterTestEnv() or a stub actor instead of calling the helper bare.","Treat a missing actor as a wiring defect, not a runtime retry condition."],"tags":["ai-video","auth","metering","credits"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}