schollz/croc · error

Stored transfers can expire after at most ${settings.maxExpi

Error message

Stored transfers can expire after at most ${settings.maxExpiresSeconds} seconds

What it means

Thrown by uploadStoredFiles() (stored.ts:592) when expiresSeconds exceeds settings.maxExpiresSeconds, the per-deployment expiry cap advertised by the storage service. maxExpiresSeconds of 0 means unlimited (the check is skipped); any positive value is a hard ceiling. The web app already mins this runtime value against the ~292-year absolute limit in App.tsx and feeds it to the expiration picker.

Source

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

  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
  ) {
    throw new Error(
      `Stored transfers can expire after at most ${settings.maxExpiresSeconds} seconds`,
    );
  }
  const key = await wasm().storeGenerateKey();
  const plan = planStoredUpload(files);
  const created = await createStoredUpload(
    key,
    plan,
    downloads,
    expiresSeconds,
    settings,
    callbacks,
    signal,
  );
  let finalized = false;
  try {
    await uploadStoredManifest(plan, created, settings, callbacks, signal);
    await uploadStoredChunks(plan, created, settings, callbacks, signal);

View on GitHub (pinned to e25f1bdc04)

Solutions

  1. Clamp: when settings.maxExpiresSeconds > 0, pass Math.min(requested, settings.maxExpiresSeconds)
  2. Drive the expiration picker maximum from the same settings value (as stored-ui.tsx does)
  3. Confirm settings come from the current service runtime, not a stale constant

Example fix

// before
await uploadStoredFiles({ files, settings, expiresSeconds: 7 * 24 * 3600 });

// after
const requested = 7 * 24 * 3600;
const expiresSeconds = settings.maxExpiresSeconds > 0 ? Math.min(requested, settings.maxExpiresSeconds) : requested;
await uploadStoredFiles({ files, settings, expiresSeconds });
Defensive patterns

Strategy: validation

Validate before calling

const cap = settings.maxExpiresSeconds;
const expiresSeconds = cap > 0 ? Math.min(requested, cap) : requested;

Type guard

function withinExpiryLimit(expiresSeconds: number, s: StoredSettings): boolean {
  return s.maxExpiresSeconds <= 0 || expiresSeconds <= s.maxExpiresSeconds;
}

Try / catch

try { await uploadStoredFiles(opts); } catch (e) { if (e instanceof Error && e.message.startsWith('Stored transfers can expire after at most')) { opts.expiresSeconds = settings.maxExpiresSeconds; return uploadStoredFiles(opts); } throw e; }

Prevention

When it happens

Trigger: Calling uploadStoredFiles with expiresSeconds: 7*24*3600 while settings.maxExpiresSeconds is 3*24*3600. Occurs when the UI offers longer expirations than the server allows or settings are stale.

Common situations: Server-side maxExpiresSeconds was reduced after the UI was built; settings fixture in tests differs from production; copied settings between environments.

Related errors


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