{"record":{"id":"58c2cf1803c344f1","repo":"paperclipai/paperclip","slug":"uploaded-but-public-verification-did-not-finish-check-url","errorCode":null,"errorMessage":"Uploaded, but public verification did not finish. Check ${url} and the CloudFront cache policy (minimum TTL must not exceed 300 seconds).","messagePattern":"Uploaded, but public verification did not finish\\. Check (.+?) and the CloudFront cache policy \\(minimum TTL must not exceed 300 seconds\\)\\.","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"scripts/publish-announcements.ts","lineNumber":125,"sourceCode":"      const body = announcementManifestSchema.parse(await response.json());\n      if (response.ok && JSON.stringify(body) === JSON.stringify(prepared.manifest)\n        && /(?:^|,)\\s*max-age=300(?:\\s*,|$)/i.test(response.headers.get(\"cache-control\") ?? \"\")) {\n        for (const asset of prepared.files.slice(0, -1)) {\n          const image = await fetch(`${baseUrl}/${asset.key}`, { method: \"HEAD\", signal: AbortSignal.timeout(10_000), credentials: \"omit\", redirect: \"error\" });\n          const caching = image.headers.get(\"cache-control\") ?? \"\";\n          if (!image.ok || image.headers.get(\"content-type\") !== asset.contentType\n            || !/(?:^|,)\\s*max-age=31536000(?:\\s*,|$)/i.test(caching)\n            || !/(?:^|,)\\s*immutable(?:\\s*,|$)/i.test(caching)) {\n            throw new Error(\"Public announcement asset headers are not ready\");\n          }\n        }\n        console.log(`Published and verified: ${url}`);\n        return;\n      }\n    } catch { /* Retry edge propagation; uploads have already completed. */ }\n    if (attempt < 22) await new Promise((resolve) => setTimeout(resolve, 15_000));\n  }\n  throw new Error(`Uploaded, but public verification did not finish. Check ${url} and the CloudFront cache policy (minimum TTL must not exceed 300 seconds).`);\n}\n\nif (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {\n  main().catch((error) => {\n    console.error(error instanceof Error ? error.message : \"Announcement publish failed\");\n    process.exitCode = 1;\n  });\n}\n","sourceCodeStart":107,"sourceCodeEnd":134,"githubUrl":"https://github.com/paperclipai/paperclip/blob/3f1d897a7c018d76563a21c6e39c3c9b03933622/scripts/publish-announcements.ts#L107-L134","documentation":"After uploads, the script retries public verification (HEAD checks on assets and current.json) up to 22 attempts 15 seconds apart. If no attempt succeeds, uploads are already done but the public URLs never verified, so this final error is thrown pointing at the URL and the CloudFront cache policy (minimum TTL must not exceed 300s).","triggerScenarios":"All ~5.5 minutes of verification attempts fail because the CDN never serves the new objects with correct headers/content-type, or current.json never reflects the new manifest within its max-age=300 window.","commonSituations":"CloudFront minimum TTL set above 300 seconds pinning stale current.json; a distribution misconfiguration serving 403/404 for the prefix; wrong PAPERCLIP_PAGE_BASE_URL so verification hits a different host than was uploaded to; S3 upload to a different bucket than the CDN origin.","solutions":["Confirm the CloudFront cache policy minimum TTL is <= 300 seconds so current.json (max-age=300) expires promptly","Manually create a CloudFront invalidation for the publish prefix and re-run verification or the script","Check curl -I <url> and the asset HEAD responses to see whether failures are 404 (propagation/origin) vs wrong headers (policy)","Verify PAPERCLIP_PAGE_BASE_URL and PAPERCLIP_PAGE_BUCKET point at the host/bucket actually backed by the CDN distribution"],"exampleFix":"// before\nCachePolicy: minTTL=3600 on current.json\n// after\nCachePolicy: minTTL=0, defaultTTL=300 for current.json; rerun publish script","handlingStrategy":"retry","validationCode":"const head = await fetch(url, { method: \"HEAD\" });\nif (!head.ok) console.warn(`current.json not yet verifiable: ${head.status}`);","typeGuard":null,"tryCatchPattern":"try { await main(); } catch (e) { if (e.message.includes(\"public verification did not finish\")) { /* uploads done: check CDN TTL/policy, invalidate cache, re-verify */ } }","preventionTips":["Keep the CloudFront minimum TTL at or below 300 seconds so current.json (max-age=300) refreshes","Ensure PAPERCLIP_PAGE_BASE_URL and PAPERCLIP_PAGE_BUCKET resolve to the same CDN-backed origin","Pre-create cache invalidations for the publish prefix in CD pipelines","Monitor the CDN for 4xx/5xx on the announcement prefix to catch policy misconfigurations early"],"tags":["cdn","cache","timeout","verification"],"backgroundTag":"request-timeout","analyzedSha":"3f1d897a7c018d76563a21c6e39c3c9b03933622","analyzedAt":"2026-09-18T08:03:59.046Z","contentChangedAt":"2026-09-18T08:03:59.046Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}