HeyPuter/puter · error · HttpError
App owner not found
Error message
App owner not found
What it means
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.
Source
Thrown at extensions/appTelemetry.ts:132
max: MAX_LIMIT,
fallback: DEFAULT_LIMIT,
});
const safeOffset = parseIntParam(offset, {
key: 'offset',
min: 0,
max: MAX_OFFSET,
fallback: 0,
});
const app = await this.stores.app.getByUid(app_uuid);
if (!app) throw new HttpError(404, 'App not found');
// The `apps-of-user:<uuid>:write` implicator keys on the owner's
// UUID, not the numeric id. Look up the owner explicitly — the raw
// app row only carries `owner_user_id`. (v1 got the owner for free
// because its entity-storage layer eager-joined the owner row.)
const ownerId = (app as { owner_user_id?: number }).owner_user_id;
if (!ownerId) throw new HttpError(404, 'App owner not found');
const owner = await this.stores.user.getById(ownerId);
if (!owner?.uuid) throw new HttpError(404, 'App owner not found');
const actor = Context.get('actor');
if (!actor) throw new HttpError(401, 'Authentication required');
const ownsApp = await this.services.permission
.check(actor as Actor, `apps-of-user:${owner.uuid}:write`)
.catch(() => false);
if (!ownsApp) throw new HttpError(403, 'Permission denied');
const appId = (app as { id: number }).id;
const users = (await this.clients.db.read(
`SELECT u.id, u.username, u.uuid, u.email FROM user_to_app_permissions p
INNER JOIN ${this.clients.db.quoteIdentifier('user')} u ON p.user_id = u.id
WHERE p.permission = 'flag:app-is-authenticated' AND p.app_id = ?
ORDER BY (p.dt IS NOT NULL), p.dt, p.user_id
LIMIT ? OFFSET ?`,View on GitHub (pinned to 908ec23eda)
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.
Example fix
// before
await gemini.generate({ prompt: userInput?.text ?? '' });
await gemini.generate({ prompt: null });
// after
const prompt = (userInput?.text ?? '').trim();
if (!prompt) throw new Error('prompt required');
await gemini.generate({ prompt }); Defensive patterns
Strategy: validation
Validate before calling
function cleanPrompt(p) {
if (typeof p !== 'string') throw new Error('prompt must be a non-empty string');
const trimmed = p.trim();
if (!trimmed) throw new Error('prompt must be a non-empty string');
return trimmed;
}
const prompt = cleanPrompt(userInput); Type guard
function isNonEmptyPrompt(p) {
return typeof p === 'string' && p.trim().length > 0;
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- Permission denied
- Authentication required
- ${key} must be an integer between ${min} and ${max}
- Authentication required
- bad_request
AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12).
Data as JSON: /api/errors/6709d9cd32c010d5.
Report an issue: GitHub.