HeyPuter/puter · critical · HttpError

unauthorized

unauthorized

Error message

actor not found in context

What it means

Thrown by GeminiImageProvider.#generateWithImagen() (GeminiImageProvider.ts:279) when Context.get('actor') is falsy. The Imagen (generateImages) path requires the actor to bill per-image usage, so a missing actor is fatal before any cost or API call. This indicates the request is not executing within the AsyncLocalStorage context that sets `actor` — typically a wiring/middleware problem, not an end-user error. HTTP 401 unauthorized.

Source

Thrown at src/backend/drivers/ai-image/providers/gemini/GeminiImageProvider.ts:279

        if (!url) {
            throw new HttpError(
                400,
                'Failed to extract image URL from Gemini response',
                { legacyCode: 'unknown_error' },
            );
        }

        return url;
    }

    async #generateWithImagen(
        prompt: string,
        selectedModel: IGeminiImageModel,
        params: IGenerateParams,
    ): Promise<string> {
        const actor = Context.get('actor');
        if (!actor) {
            throw new HttpError(401, 'actor not found in context', {
                legacyCode: 'unauthorized',
            });
        }
        const costCents = selectedModel.costs?.['per-image'];
        if (costCents === undefined) {
            throw new HttpError(
                400,
                `No per-image cost configured for model '${selectedModel.id}'`,
                { legacyCode: 'bad_request' },
            );
        }
        const costInMicroCents = Math.ceil(costCents * 1_000_000);

        const usageAllowed = await this.#meteringService.hasEnoughCredits(
            actor,
            costInMicroCents,
        );
        if (!usageAllowed) {

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Ensure the provider is only called within a request context that sets actor via Context (the controller layer normally does this).
  2. In tests, wrap the call: `await Context.run({ actor: testActor }, async () => provider.generate(...))` or use setupPuterTestEnv().
  3. Verify the controller/driver middleware sets Context actor before delegating to the provider.
  4. If calling from a background job, explicitly establish an actor in the context before invoking generate().

Example fix

// before (test or background path — actor missing)
await provider.generate({ prompt: 'x', model: 'imagen-4.0-fast-generate-001' });

// after
import { Context } from '../../core/context.js';
await Context.run({ actor: testActor }, async () => {
  await provider.generate({ prompt: 'x', model: 'imagen-4.0-fast-generate-001' });
});
Defensive patterns

Strategy: validation

Validate before calling

import { Context } from '../../core/context.js';
function ensureActor(): Actor {
  const actor = Context.get('actor');
  if (!actor) throw new Error('actor not found in context — wrap call in Context.run');
  return actor;
}

Prevention

When it happens

Trigger: Calling #generateWithImagen (via generate() with a model whose apiType is 'generateImages') outside of a request lifecycle that populates Context; a background job or test harness invoking the provider without running Context.run(); middleware ordering that reads actor before it is set; a code path that bypasses the standard controller context setup.

Common situations: Unit/integration tests calling the provider directly without setupPuterTestEnv() or Context.run({actor}, ...); a refactored controller that lost the ALS context propagation; a cron/internal task invoking image generation without establishing an actor.

Related errors


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