HeyPuter/puter · error · HttpError

App not found

Error message

App not found

What it means

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.

Source

Thrown at extensions/appTelemetry.ts:125

        Array<{ user: string; user_uuid: string; user_email?: string | null }>
    > {
        if (!app_uuid) throw new HttpError(400, 'Missing `app_uuid`');

        const safeLimit = parseIntParam(limit, {
            key: 'limit',
            min: 1,
            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;

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Route the call through the normal authenticated controller/middleware so Context.actor is set.
  2. If invoking capSecondsToRemainingCredits directly, pass the actor explicitly from Context.get('actor') and assert it is present first.
  3. In tests, use setupPuterTestEnv() / a stub Context actor rather than calling the helper bare.
  4. Audit the calling endpoint's RouteOptions to confirm the auth gate is enabled.

Example fix

// before — called without actor context
const allowed = await capSecondsToRemainingCredits({
  metering, actor: undefined, perSecondMicroCents, requestedSeconds: durationSeconds,
});

// after — require actor up front
const actor = Context.get('actor');
if (!actor) throw new HttpError(401, 'Authentication required', { legacyCode: 'unauthorized' });
const allowed = await capSecondsToRemainingCredits({
  metering, actor, perSecondMicroCents, requestedSeconds: durationSeconds,
});
Defensive patterns

Strategy: validation

Validate before calling

const actor = Context.get('actor');
if (!actor) throw new HttpError(401, 'Authentication required', { legacyCode: 'unauthorized' });
const allowed = await capSecondsToRemainingCredits({ metering, actor, perSecondMicroCents, requestedSeconds });

Type guard

function hasActor(ctx) {
  const a = ctx.get('actor');
  return !!a && (typeof a === 'object') && ('id' in a || 'user' in a);
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of HeyPuter/puter@908ec23eda (2026-08-12). Data as JSON: /api/errors/50a934befb3e97ed. Report an issue: GitHub.