schollz/croc · error

Stored-transfer downloads must be a positive integer

Error message

Stored-transfer downloads must be a positive integer

What it means

Thrown by uploadStoredFiles() (web/src/protocol/stored.ts:575) when the `downloads` option is not a safe positive integer. The library validates client-side before any network call because the storage service only permits whole download counts starting at 1. Values like 0, -1, 2.5, NaN, Infinity, or undefined all fail the Number.isSafeInteger / < 1 checks.

Source

Thrown at web/src/protocol/stored.ts:576

export async function uploadStoredFiles(options: {
  files: StoredPreparedFile[];
  settings: StoredSettings;
  downloads?: number;
  expiresSeconds?: number;
  callbacks?: StoredUploadCallbacks;
  signal?: AbortSignal;
}) {
  const {
    files,
    settings,
    downloads = 1,
    expiresSeconds = 24 * 60 * 60,
    callbacks = {},
    signal,
  } = options;
  if (!Number.isSafeInteger(downloads) || downloads < 1) {
    throw new Error("Stored-transfer downloads must be a positive integer");
  }
  if (downloads > settings.maxDownloads) {
    throw new Error(
      `Stored transfers can allow at most ${settings.maxDownloads} downloads`,
    );
  }
  if (
    !Number.isSafeInteger(expiresSeconds) ||
    expiresSeconds < 60 ||
    expiresSeconds > 9_223_372_036
  ) {
    throw new Error(
      "Stored-transfer expiration must be a whole number of seconds of at least one minute",
    );
  }
  if (
    settings.maxExpiresSeconds > 0 &&
    expiresSeconds > settings.maxExpiresSeconds

View on GitHub (pinned to e25f1bdc04)

Solutions

  1. Validate/clamp before the call: whole number >= 1 (the option defaults to 1, so omit it when unsure)
  2. Derive from UI input with Math.max(1, Math.floor(value)) and re-check Number.isSafeInteger
  3. Type-guard values deserialized from JSON before passing them into options

Example fix

// before
await uploadStoredFiles({ files, settings, downloads: Number(input.value) });

// after
const downloads = Number(input.value);
if (!Number.isSafeInteger(downloads) || downloads < 1) throw new RangeError('downloads must be a whole number >= 1');
await uploadStoredFiles({ files, settings, downloads });
Defensive patterns

Strategy: validation

Validate before calling

const downloads = Number(raw);
if (!Number.isSafeInteger(downloads) || downloads < 1) {
  throw new RangeError(`downloads must be a whole number >= 1, got ${raw}`);
}

Type guard

function isValidDownloadCount(v: unknown): v is number {
  return typeof v === 'number' && Number.isSafeInteger(v) && v >= 1;
}

Try / catch

try { await uploadStoredFiles(opts); } catch (e) { if (e instanceof Error && e.message === 'Stored-transfer downloads must be a positive integer') { flagField('downloads'); return; } throw e; }

Prevention

When it happens

Trigger: Calling uploadStoredFiles({ files, settings, downloads: X }) with X = 0, negative, fractional (e.g. 1.5), NaN, Infinity, or undefined/non-number. Typical when the value comes from a UI slider defaulting to 0, from parseInt of user input, or from untyped deserialized JSON.

Common situations: A number-of-downloads picker whose default is 0 before the user selects; reading the field out of localStorage/sessionStorage receipts where it may be missing; passing a string like '3' (Number.isSafeInteger('3') is false).

Related errors


AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15). Data as JSON: /api/errors/154d4b2b40597170. Report an issue: GitHub.