can1357/oh-my-pi · error

tmpfiles option ttl must be between 60 seconds and 48 hours

Error message

tmpfiles option ttl must be between 60 seconds and 48 hours

What it means

After parsing the tmpfiles ttl duration, the runtime clamps validation to the host's accepted range: between 60 seconds and 48 hours (172800 seconds). Durations outside this range throw, since tmpfiles.org will not honor them. Note "24" without a unit means 24 seconds and fails this check.

Source

Thrown at packages/coding-agent/src/blob-broker/uploaders-anonymous.ts:157

		async upload(request: BlobUploadRequest) {
			const { url } = await uploadTextUrl("uguu", config, UGUU_UPLOAD_URL, multipartFile(request, "files[]"));
			return publication("uguu", request, url, {
				expiresAt: Date.now() + 3 * HOUR_MS,
				remoteId: remoteName(url),
			});
		},
	};
}

function tmpfilesTtlSeconds(config: DestinationRuntimeConfig): number {
	const ttl = optionString(config, "ttl", "1h")?.trim().toLowerCase();
	const match = ttl?.match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)?$/);
	if (!match) throw new Error("tmpfiles option ttl must be a duration such as 1h");
	const amount = Number(match[1]);
	const multiplier = match[2] === "d" ? 86_400 : match[2] === "h" ? 3_600 : match[2] === "m" ? 60 : 1;
	const seconds = Math.round(amount * multiplier);
	if (!Number.isFinite(seconds) || seconds < 60 || seconds > 172_800) {
		throw new Error("tmpfiles option ttl must be between 60 seconds and 48 hours");
	}
	return seconds;
}

function recordValue(value: unknown): Readonly<Record<string, unknown>> | undefined {
	return typeof value === "object" && value !== null && !Array.isArray(value)
		? (value as Readonly<Record<string, unknown>>)
		: undefined;
}

function stringAtPath(value: unknown, path: string): string | undefined {
	let current = value;
	for (const segment of path.split(".").filter(Boolean)) {
		if (Array.isArray(current)) {
			if (!/^\d+$/.test(segment)) return undefined;
			current = current[Number(segment)];
			continue;
		}

View on GitHub (pinned to 9690622007)

Solutions

  1. Set ttl between "1m" and "48h" (e.g. "1h", "12h", "48h").
  2. Add a unit when you mean hours: "24" → "24h".
  3. For retention beyond 48h, pick a different destination that supports longer expiry.

Example fix

// before
{ "options": { "ttl": "3d" } }
// after
{ "options": { "ttl": "48h" } }
Defensive patterns

Strategy: validation

Validate before calling

function parseTtlSeconds(ttl: string): number {
  const m = ttl.trim().toLowerCase().match(/^(\d+(?:\.\d+)?)\s*(s|m|h|d)?$/);
  if (!m) throw new Error(`bad ttl: ${ttl}`);
  const mult = m[2] === "d" ? 86400 : m[2] === "h" ? 3600 : m[2] === "m" ? 60 : 1;
  return Math.round(Number(m[1]) * mult);
}
const seconds = parseTtlSeconds(String(config.options.ttl ?? "1h"));
if (seconds < 60 || seconds > 172800) throw new Error(`tmpfiles ttl must be 60s–48h, got ${seconds}s`);

Type guard

const isAcceptableTtl = (v: unknown): boolean => { try { const s = parseTtlSeconds(String(v)); return s >= 60 && s <= 172800; } catch { return false; } };

Try / catch

try {
  await uploadTmpfiles(config, req);
} catch (err) {
  if (String(err).includes("between 60 seconds and 48 hours")) {
    logger.warn("ttl out of range for tmpfiles; clamping to 48h");
    config.options.ttl = "48h";
  } else throw err;
}

Prevention

When it happens

Trigger: tmpfiles `ttl` parses as a valid duration but rounds outside [60, 172800] seconds — e.g. "30s", "24" (interpreted as 24 seconds), "3d" (259200s), "100h", or a fractional value like "0.5m" (30s).

Common situations: Users wanting longer retention than the host allows ("3d", "1w"), specifying too-short retention ("10s", "30s"), or forgetting the unit so a number becomes seconds.

Related errors


AI-assisted analysis of can1357/oh-my-pi@9690622007 (2026-08-31). Data as JSON: /api/errors/2a5b263605902fda. Report an issue: GitHub.