{"record":{"id":"2a5b263605902fda","repo":"can1357/oh-my-pi","slug":"tmpfiles-option-ttl-must-be-between-60-seconds-and","errorCode":null,"errorMessage":"tmpfiles option ttl must be between 60 seconds and 48 hours","messagePattern":"tmpfiles option ttl must be between 60 seconds and 48 hours","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/uploaders-anonymous.ts","lineNumber":157,"sourceCode":"\t\tasync upload(request: BlobUploadRequest) {\n\t\t\tconst { url } = await uploadTextUrl(\"uguu\", config, UGUU_UPLOAD_URL, multipartFile(request, \"files[]\"));\n\t\t\treturn publication(\"uguu\", request, url, {\n\t\t\t\texpiresAt: Date.now() + 3 * HOUR_MS,\n\t\t\t\tremoteId: remoteName(url),\n\t\t\t});\n\t\t},\n\t};\n}\n\nfunction tmpfilesTtlSeconds(config: DestinationRuntimeConfig): number {\n\tconst ttl = optionString(config, \"ttl\", \"1h\")?.trim().toLowerCase();\n\tconst match = ttl?.match(/^(\\d+(?:\\.\\d+)?)\\s*(s|m|h|d)?$/);\n\tif (!match) throw new Error(\"tmpfiles option ttl must be a duration such as 1h\");\n\tconst amount = Number(match[1]);\n\tconst multiplier = match[2] === \"d\" ? 86_400 : match[2] === \"h\" ? 3_600 : match[2] === \"m\" ? 60 : 1;\n\tconst seconds = Math.round(amount * multiplier);\n\tif (!Number.isFinite(seconds) || seconds < 60 || seconds > 172_800) {\n\t\tthrow new Error(\"tmpfiles option ttl must be between 60 seconds and 48 hours\");\n\t}\n\treturn seconds;\n}\n\nfunction recordValue(value: unknown): Readonly<Record<string, unknown>> | undefined {\n\treturn typeof value === \"object\" && value !== null && !Array.isArray(value)\n\t\t? (value as Readonly<Record<string, unknown>>)\n\t\t: undefined;\n}\n\nfunction stringAtPath(value: unknown, path: string): string | undefined {\n\tlet current = value;\n\tfor (const segment of path.split(\".\").filter(Boolean)) {\n\t\tif (Array.isArray(current)) {\n\t\t\tif (!/^\\d+$/.test(segment)) return undefined;\n\t\t\tcurrent = current[Number(segment)];\n\t\t\tcontinue;\n\t\t}","sourceCodeStart":139,"sourceCodeEnd":175,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/uploaders-anonymous.ts#L139-L175","documentation":"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.","triggerScenarios":"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).","commonSituations":"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.","solutions":["Set ttl between \"1m\" and \"48h\" (e.g. \"1h\", \"12h\", \"48h\").","Add a unit when you mean hours: \"24\" → \"24h\".","For retention beyond 48h, pick a different destination that supports longer expiry."],"exampleFix":"// before\n{ \"options\": { \"ttl\": \"3d\" } }\n// after\n{ \"options\": { \"ttl\": \"48h\" } }","handlingStrategy":"validation","validationCode":"function parseTtlSeconds(ttl: string): number {\n  const m = ttl.trim().toLowerCase().match(/^(\\d+(?:\\.\\d+)?)\\s*(s|m|h|d)?$/);\n  if (!m) throw new Error(`bad ttl: ${ttl}`);\n  const mult = m[2] === \"d\" ? 86400 : m[2] === \"h\" ? 3600 : m[2] === \"m\" ? 60 : 1;\n  return Math.round(Number(m[1]) * mult);\n}\nconst seconds = parseTtlSeconds(String(config.options.ttl ?? \"1h\"));\nif (seconds < 60 || seconds > 172800) throw new Error(`tmpfiles ttl must be 60s–48h, got ${seconds}s`);","typeGuard":"const isAcceptableTtl = (v: unknown): boolean => { try { const s = parseTtlSeconds(String(v)); return s >= 60 && s <= 172800; } catch { return false; } };","tryCatchPattern":"try {\n  await uploadTmpfiles(config, req);\n} catch (err) {\n  if (String(err).includes(\"between 60 seconds and 48 hours\")) {\n    logger.warn(\"ttl out of range for tmpfiles; clamping to 48h\");\n    config.options.ttl = \"48h\";\n  } else throw err;\n}","preventionTips":["Keep tmpfiles ttl within 1m–48h; clamp user-supplied durations in your own loader.","Remember a bare number is seconds: \"24\" is 24s and fails the lower bound.","For retention beyond 48h, switch to a destination with longer expiry support."],"tags":["config","validation","duration","range"],"backgroundTag":"invalid-duration-format","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}