{"record":{"id":"0ed420c1eff114fd","repo":"can1357/oh-my-pi","slug":"anthropic-file-upload-returned-an-invalid-expirati","errorCode":null,"errorMessage":"Anthropic file upload returned an invalid expiration time","messagePattern":"Anthropic file upload returned an invalid expiration time","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"packages/coding-agent/src/blob-broker/provider-files-anthropic.ts","lineNumber":79,"sourceCode":"\t\ttypeof record.size_bytes !== \"number\" ||\n\t\t!Number.isSafeInteger(record.size_bytes) ||\n\t\trecord.size_bytes < 0 ||\n\t\t!(record.expires_at === undefined || record.expires_at === null || typeof record.expires_at === \"string\")\n\t) {\n\t\tthrow new Error(\"Anthropic file upload returned invalid metadata\");\n\t}\n\treturn {\n\t\tid: record.id,\n\t\tmime_type: record.mime_type,\n\t\tsize_bytes: record.size_bytes,\n\t\texpires_at: record.expires_at,\n\t};\n}\n\nfunction parseExpiresAt(value: string | null | undefined): number | undefined {\n\tif (value == null) return undefined;\n\tconst expiresAt = Date.parse(value);\n\tif (!Number.isFinite(expiresAt)) throw new Error(\"Anthropic file upload returned an invalid expiration time\");\n\treturn expiresAt;\n}\n\nfunction uploadedFile(request: ProviderFileUploadRequest): File {\n\tconst preferred = request.filename?.trim().replaceAll(\"\\\\\", \"/\").split(\"/\").pop();\n\tconst filename = preferred && preferred !== \".\" && preferred !== \"..\" ? preferred : \"upload\";\n\treturn new File([request.bytes], filename, { type: request.mimeType });\n}\n\n/**\n * Create a native Anthropic Files API client for an official Anthropic Messages model.\n * Unsupported providers, APIs, and non-Anthropic endpoints return `null` without making a request.\n */\nexport function createAnthropicFileClient(\n\tmodel: Model,\n\tcredential: string,\n\tfetchImpl: FetchImpl = globalThis.fetch,\n): ProviderFileClient | null {","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/can1357/oh-my-pi/blob/969062200754ea02cfac922e5ebb8c608c079e15/packages/coding-agent/src/blob-broker/provider-files-anthropic.ts#L61-L97","documentation":"After a successful upload, the client converts the `expires_at` string from the Anthropic Files API metadata into a numeric epoch-milliseconds timestamp via `Date.parse`. If the string is present but cannot be parsed into a finite date (unparseable, empty-but-nonnull, or malformed format), the client throws instead of returning a bogus expiry that would break downstream TTL/retention logic.","triggerScenarios":"`parseExpiresAt` is called during `upload` with a `metadata.expires_at` value that is a non-null string but not a parseable RFC/date-time string — e.g. `\"\"`, `\"null\"`, `\"undefined\"`, `\"2026-13-45T99:00:00Z\"`, or a locale-formatted date.","commonSituations":"Anthropic changes the expires_at wire format (e.g. from ISO-8601 to epoch seconds as a string); a mock/fixture records expires_at as something like \"never\" or an empty string; a proxy mangles the JSON; an environment with a patched Date.parse or exotic locale assumptions misinterprets an otherwise valid date string.","solutions":["Log the exact `expires_at` value from the upload response and verify it is an ISO-8601 date-time string (e.g. 2026-08-30T12:00:00Z) that `new Date(value).toString()` does not return 'Invalid Date'.","Fix any mock or recorded fixture so expires_at is a real ISO-8601 string, or omit the field entirely (null/undefined is treated as no expiry, which is valid).","Update the package if Anthropic changed the timestamp format — the parser may need to accept the new format.","Check that no middleware or proxy is corrupting the response body between api.anthropic.com and your process."],"exampleFix":null,"handlingStrategy":"validation","validationCode":"// check expiry strings from the API before passing them on\nfunction isParseableDate(v: string | null | undefined): boolean {\n  if (v == null) return true;\n  return Number.isFinite(Date.parse(v));\n}","typeGuard":"const hasValidExpiry = (m: { expires_at?: string | null }): boolean => m.expires_at == null || Number.isFinite(Date.parse(m.expires_at));","tryCatchPattern":"try {\n  const handle = await anthropicFiles.upload(request);\n} catch (err) {\n  if (err instanceof Error && err.message.includes(\"invalid expiration time\")) {\n    logger.error(\"Anthropic expires_at unparseable — API format may have changed\", { cause: err });\n    // treat as unknown expiry or re-upload\n  } else throw err;\n}","preventionTips":["Keep fixtures/mocks realistic: expires_at must be ISO-8601 (e.g. 2026-08-30T12:00:00Z), never \"never\" or \"\".","When upgrading Anthropic API versions, diff the expires_at format against what your parser accepts.","Prefer omitting expires_at (null/undefined is valid) over fabricating placeholder strings in tests.","Sanity-check parsed timestamps downstream (positive, in the future) to catch format drift early."],"tags":["anthropic","files-api","date-parsing","upload"],"backgroundTag":"invalid-date-format","analyzedSha":"969062200754ea02cfac922e5ebb8c608c079e15","analyzedAt":"2026-08-31T10:29:35.737Z","schemaVersion":2},"datasetVersion":"2026-08-31T14:17:45.589Z"}