{"record":{"id":"6709d9cd32c010d5","repo":"HeyPuter/puter","slug":"app-owner-not-found","errorCode":null,"errorMessage":"App owner not found","messagePattern":"App owner not found","errorType":"http","errorClass":"HttpError","httpStatus":404,"severity":"error","filePath":"extensions/appTelemetry.ts","lineNumber":132,"sourceCode":"            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;\n\n        const users = (await this.clients.db.read(\n            `SELECT u.id, u.username, u.uuid, u.email FROM user_to_app_permissions p\n             INNER JOIN ${this.clients.db.quoteIdentifier('user')} u ON p.user_id = u.id\n             WHERE p.permission = 'flag:app-is-authenticated' AND p.app_id = ?\n             ORDER BY (p.dt IS NOT NULL), p.dt, p.user_id\n             LIMIT ? OFFSET ?`,","sourceCodeStart":114,"sourceCodeEnd":150,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/extensions/appTelemetry.ts#L114-L150","documentation":"GeminiVideoProvider.generate validates that `prompt` is a non-empty string before resolving the model or honoring test_mode. Anything that is not a string, or a string of only whitespace, is rejected with 400 bad_request. This is the first guard in generate() and runs unconditionally.","triggerScenarios":"Calling the Gemini video generate path with prompt omitted, null, undefined, a number/object, or an empty/whitespace string. Commonly a missing form field, a default value never set, or prompt built from an empty template literal.","commonSituations":"Frontend submits the form before the user typed a prompt; prompt sourced from a config/env var that is unset; prompt passed positionally in the wrong slot of the params object.","solutions":["Ensure the prompt is a trimmed non-empty string before calling generate().","Disable the submit action in the UI until the prompt field has non-whitespace content.","Coerce untrusted input with `String(value).trim()` and reject empty upstream of the API.","Cross-check the param order against IGenerateVideoParams — prompt belongs under the `prompt` key."],"exampleFix":"// before\nawait gemini.generate({ prompt: userInput?.text ?? '' });\nawait gemini.generate({ prompt: null });\n\n// after\nconst prompt = (userInput?.text ?? '').trim();\nif (!prompt) throw new Error('prompt required');\nawait gemini.generate({ prompt });","handlingStrategy":"validation","validationCode":"function cleanPrompt(p) {\n  if (typeof p !== 'string') throw new Error('prompt must be a non-empty string');\n  const trimmed = p.trim();\n  if (!trimmed) throw new Error('prompt must be a non-empty string');\n  return trimmed;\n}\nconst prompt = cleanPrompt(userInput);","typeGuard":"function isNonEmptyPrompt(p) {\n  return typeof p === 'string' && p.trim().length > 0;\n}","tryCatchPattern":null,"preventionTips":["Disable the submit control until the prompt field has non-whitespace text.","Trim and validate at the form boundary, mirroring the server's check.","Reject empty prompts before queueing a generation job."],"tags":["ai-video","gemini","validation","prompt"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}