schollz/croc · error
Storage service returned an invalid expiration time
Error message
Storage service returned an invalid expiration time
What it means
completeStoredUpload() finishes the upload by POSTing /complete and reading {expiresAt} from the response. The value must be a parseable date (Number.isFinite(Date.parse(...))); a missing field, empty string, or malformed timestamp throws before the share link is returned, and the upload's finally-block then revokes the transfer.
Source
Thrown at web/src/protocol/stored.ts:554
}
}
}
async function completeStoredUpload(
created: CreatedStoredUpload,
settings: StoredSettings,
callbacks: StoredUploadCallbacks,
signal?: AbortSignal,
) {
callbacks.onStatus?.("Finalizing encrypted transfer…");
const response = await authorizedFetch(
api(settings, `/${created.share.id}/complete`),
created.uploadToken,
{ method: "POST", signal },
);
const result = (await response.json()) as { expiresAt: string };
if (!Number.isFinite(Date.parse(result.expiresAt))) {
throw new Error("Storage service returned an invalid expiration time");
}
return result.expiresAt;
}
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 = {},View on GitHub (pinned to e25f1bdc04)
Solutions
- Update the storage service to return {"expiresAt": "<ISO 8601 timestamp>"} from POST /transfers/{id}/complete
- Check the raw response (network tab) for an unexpected body and fix whatever middleware is intercepting it
- Retry after fixing the server — note the client auto-revokes the half-finished transfer, so a fresh upload is required
Defensive patterns
Strategy: try-catch
Try / catch
try { expiresAt = await completeStoredUpload(...); } catch (e) { if (e instanceof Error && e.message.includes("expiration")) { logServerContractViolation("expiresAt"); } throw e; } Prevention
- Make POST /transfers/{id}/complete return {"expiresAt": "<ISO 8601>"} in tests and production
- Remember a failed finalize auto-revokes the transfer — fix the server, then upload fresh
When it happens
Trigger: The /complete response JSON lacks expiresAt or contains a non-ISO string — server bug, or a proxy/error page replacing the JSON while fetch still saw status 2xx.
Common situations: Custom or older storage services that return 204 No Content or a different schema for /complete; gateways substituting a captive-portal 200 page; version skew after an API change to the completion response.
Related errors
- Storage service returned unsupported chunk size ${created.ch
- Storage service returned an invalid upload capability
- Storage service returned an invalid download count
- Storage service created ${acceptedDownloads} downloads inste
- Peer sent an unexpected control handshake
AI-assisted analysis of schollz/croc@e25f1bdc04 (2026-08-15).
Data as JSON: /api/errors/16815c69047962f4.
Report an issue: GitHub.