{"record":{"id":"443354319503807d","repo":"HeyPuter/puter","slug":"key-must-be-an-integer-between-min-and-max","errorCode":null,"errorMessage":"${key} must be an integer between ${min} and ${max}","messagePattern":"(.+?) must be an integer between (.+?) and (.+?)","errorType":"http","errorClass":"HttpError","httpStatus":400,"severity":"error","filePath":"extensions/appTelemetry.ts","lineNumber":49,"sourceCode":"        min,\n        max,\n        fallback,\n    }: { key: string; min: number; max: number; fallback: number },\n): number => {\n    if (value === undefined || value === null) return fallback;\n    const parsed =\n        typeof value === 'number'\n            ? value\n            : typeof value === 'string' && value.trim() !== ''\n              ? Number(value)\n              : NaN;\n    if (\n        !Number.isFinite(parsed) ||\n        !Number.isInteger(parsed) ||\n        parsed < min ||\n        parsed > max\n    ) {\n        throw new HttpError(\n            400,\n            `${key} must be an integer between ${min} and ${max}`,\n        );\n    }\n    return parsed;\n};\n\n/**\n * Driver exposing the `app-telemetry` interface.\n *\n * The `/drivers/call` permission gate checks\n * `service:app-telemetry:ii:app-telemetry`, which every actor already holds via\n * the blanket `service` grant (hardcoded-permissions.js +\n * `default_implicit_user_app_permissions`). The real authorization — \"is the\n * caller the app owner?\" — is enforced inside `get_users` below, exactly as v1\n * did.\n */\nexport class AppTelemetryDriver extends PuterDriver {","sourceCodeStart":31,"sourceCodeEnd":67,"githubUrl":"https://github.com/HeyPuter/puter/blob/908ec23eda38526170322c3edf71ba45ecb1ca95/extensions/appTelemetry.ts#L31-L67","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Pass a destination under a user-owned directory, e.g. `~/Videos/<name>.mp4` or an absolute app folder path.","Validate the path client-side before the call: it must not be '/' and dirname(path) must not be '/'.","If the path originates from user input, default it to a known folder (the user's home / a 'Videos' subdir) when blank.","Log the resolved path at the call site to catch normalization bugs (e.g. trailing-slash or empty-string collapse)."],"exampleFix":"// before\nawait puter.ai.txt2video({ prompt, path: '/' });\nawait puter.ai.txt2video({ prompt, path: '' });\nawait puter.ai.txt2video({ prompt, path: '/clip.mp4' });\n\n// after\nawait puter.ai.txt2video({ prompt, path: '~/Videos/clip.mp4' });","handlingStrategy":"validation","validationCode":"import pathPosix from 'node:path/posix';\nfunction assertNonRootDestination(p) {\n  const resolved = pathPosix.normalize(p ?? '/');\n  if (resolved === '/' || pathPosix.dirname(resolved) === '/') {\n    throw new Error(`Destination must not be root or a direct child of root: ${p}`);\n  }\n  return resolved;\n}\n// before submit\nassertNonRootDestination(destPath);","typeGuard":"function isNonRootPath(p) {\n  if (typeof p !== 'string' || p.trim() === '') return false;\n  const n = p.replace(/\\/+$/, '') || '/';\n  if (n === '/') return false;\n  const slash = n.lastIndexOf('/');\n  return slash > 0; // has a non-root parent directory\n}","tryCatchPattern":"try { await puter.ai.txt2video({ prompt, path: dest }); }\ncatch (e) {\n  if (e?.code === 'cannot_write_to_root') { dest = `~/Videos/${name}.mp4`; /* retry */ }\n  else throw e;\n}","preventionTips":["Always qualify video destinations with a user-owned folder prefix.","Normalize and validate paths at the form boundary, not at the API call site only.","Treat a blank path field as a missing required field."],"tags":["ai-video","filesystem","validation","path"],"backgroundTag":null,"analyzedSha":"908ec23eda38526170322c3edf71ba45ecb1ca95","analyzedAt":"2026-08-12T20:53:15.911Z","schemaVersion":2},"datasetVersion":"2026-08-12T23:17:12.415Z"}