HeyPuter/puter · error · HttpError

bad_request

bad_request

Error message

`prompt` must be a string

What it means

Thrown by OpenAiImageProvider.generate() (OpenAiImageProvider.ts:110) when `prompt` is not a string. Unlike the Gemini and Replicate providers, this check only verifies typeof === 'string' — it does NOT reject empty strings, so prompt: '' passes this guard and proceeds to the OpenAI API. The check fires after test_mode and after the input_image/input_images merge.

Source

Thrown at src/backend/drivers/ai-image/providers/openai/OpenAiImageProvider.ts:110

        input_images,
        input_image_mime_type,
    }: IGenerateParams) {
        const selectedModel =
            this.models().find((m) => m.id === model) ||
            this.models().find((m) => m.id === this.getDefaultModel())!;

        if (test_mode) {
            return 'https://puter-sample-data.puter.site/image_example.png';
        }

        // Backwards compat: fold singular `input_image` into `input_images`.
        if (input_image && (!input_images || input_images.length === 0)) {
            input_images = [input_image];
        }
        const hasInputImages = (input_images?.length ?? 0) > 0;

        if (typeof prompt !== 'string') {
            throw new HttpError(400, '`prompt` must be a string', {
                legacyCode: 'bad_request',
            });
        }

        const validRatios = selectedModel?.allowedRatios;
        if (validRatios) {
            if (
                !ratio ||
                !validRatios.some((r) => r.w === ratio.w && r.h === ratio.h)
            ) {
                ratio = validRatios[0]; // Default to the first allowed ratio
            }
        } else {
            // Open-ended size models (gpt-image-2): conform to OpenAI's size
            // rules (16px multiples, 3840 cap, 3:1 ratio, pixel budget).
            ratio = this.#normalizeGptImage2Ratio(ratio);
        }

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Ensure the caller passes a string prompt (validate before calling generate).
  2. Add a non-empty check at the controller boundary since this provider permits empty strings.
  3. Coerce or reject non-string prompt values upstream.

Example fix

// before
await provider.generate({ prompt: formData.prompt });  // could be undefined

// after
if (typeof formData.prompt !== 'string' || formData.prompt.trim().length === 0) {
  throw new HttpError(400, 'prompt is required', { legacyCode: 'bad_request' });
}
await provider.generate({ prompt: formData.prompt });
Defensive patterns

Strategy: validation

Validate before calling

function assertStringPrompt(prompt: unknown): asserts prompt is string {
  if (typeof prompt !== 'string') {
    throw new Error('`prompt` must be a string');
  }
}
// Note: also add a non-empty check since this provider allows empty strings:
if (typeof params.prompt !== 'string' || params.prompt.trim().length === 0) {
  throw new Error('prompt is required');
}

Type guard

function isStringPrompt(prompt: unknown): prompt is string {
  return typeof prompt === 'string';
}

Try / catch

try {
  await provider.generate(params);
} catch (e) {
  if (e instanceof HttpError && e.status_code === 400 && e.message.includes('prompt')) {
    // coerce or reject prompt upstream
  }
  throw e;
}

Prevention

When it happens

Trigger: Calling generate() with prompt: undefined, prompt: null, prompt: 123, prompt: {}, or prompt: []; passing prompt: '' will NOT trigger this (empty string is allowed here).

Common situations: Caller omits prompt from the params object (becomes undefined); a deserialization or form-parsing bug yields a non-string; programmatic pipeline passing through an unvalidated variable.

Related errors


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