HeyPuter/puter · error · HttpError

${key} must be an integer between ${min} and ${max}

Error message

${key} must be an integer between ${min} and ${max}

What it means

Thrown by VideoGenerationDriver.#assertWriteAccess when the resolved destination path is the filesystem root ('/') or its parent is root (the file would land as a direct child of root). Puter forbids writes at the root namespace level; output must live under a user-owned directory. This guard runs before the ACL check so a root destination fails fast with a clear 400 rather than a permission error.

Source

Thrown at extensions/appTelemetry.ts:49

        min,
        max,
        fallback,
    }: { key: string; min: number; max: number; fallback: number },
): number => {
    if (value === undefined || value === null) return fallback;
    const parsed =
        typeof value === 'number'
            ? value
            : typeof value === 'string' && value.trim() !== ''
              ? Number(value)
              : NaN;
    if (
        !Number.isFinite(parsed) ||
        !Number.isInteger(parsed) ||
        parsed < min ||
        parsed > max
    ) {
        throw new HttpError(
            400,
            `${key} must be an integer between ${min} and ${max}`,
        );
    }
    return parsed;
};

/**
 * Driver exposing the `app-telemetry` interface.
 *
 * The `/drivers/call` permission gate checks
 * `service:app-telemetry:ii:app-telemetry`, which every actor already holds via
 * the blanket `service` grant (hardcoded-permissions.js +
 * `default_implicit_user_app_permissions`). The real authorization — "is the
 * caller the app owner?" — is enforced inside `get_users` below, exactly as v1
 * did.
 */
export class AppTelemetryDriver extends PuterDriver {

View on GitHub (pinned to 908ec23eda)

Solutions

  1. Pass a destination under a user-owned directory, e.g. `~/Videos/<name>.mp4` or an absolute app folder path.
  2. Validate the path client-side before the call: it must not be '/' and dirname(path) must not be '/'.
  3. If the path originates from user input, default it to a known folder (the user's home / a 'Videos' subdir) when blank.
  4. Log the resolved path at the call site to catch normalization bugs (e.g. trailing-slash or empty-string collapse).

Example fix

// before
await puter.ai.txt2video({ prompt, path: '/' });
await puter.ai.txt2video({ prompt, path: '' });
await puter.ai.txt2video({ prompt, path: '/clip.mp4' });

// after
await puter.ai.txt2video({ prompt, path: '~/Videos/clip.mp4' });
Defensive patterns

Strategy: validation

Validate before calling

import pathPosix from 'node:path/posix';
function assertNonRootDestination(p) {
  const resolved = pathPosix.normalize(p ?? '/');
  if (resolved === '/' || pathPosix.dirname(resolved) === '/') {
    throw new Error(`Destination must not be root or a direct child of root: ${p}`);
  }
  return resolved;
}
// before submit
assertNonRootDestination(destPath);

Type guard

function isNonRootPath(p) {
  if (typeof p !== 'string' || p.trim() === '') return false;
  const n = p.replace(/\/+$/, '') || '/';
  if (n === '/') return false;
  const slash = n.lastIndexOf('/');
  return slash > 0; // has a non-root parent directory
}

Try / catch

try { await puter.ai.txt2video({ prompt, path: dest }); }
catch (e) {
  if (e?.code === 'cannot_write_to_root') { dest = `~/Videos/${name}.mp4`; /* retry */ }
  else throw e;
}

Prevention

When it happens

Trigger: Calling puter.ai.txt2video(...) / the video-generation driver with a destination `path` that resolves to '/' (e.g. empty string normalized to root) or to '/<filename>' with no parent directory component. Happens when the caller omits the directory portion or passes a user-supplied path that trims to root.

Common situations: Frontend uses an empty/default `path` field that normalizes to '/'; building the destination from untrusted input without appending a folder; migrating from a v1 API that tolerated root writes; misconfigured default-destination config value.

Related errors


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