paperclipai/paperclip · error · Error
Public announcement asset headers are not ready
Error message
Public announcement asset headers are not ready
What it means
After uploading, the script verifies each asset at the public CDN with a HEAD request and requires HTTP 200, the exact expected Content-Type, and Cache-Control containing both max-age=31536000 and immutable. This error means at least one uploaded asset failed those header checks during a verification attempt (retried for edge propagation).
Solutions
- Check the CDN response headers directly: curl -I <baseUrl>/<key> and compare content-type and cache-control with the script's expectations
- Set the CloudFront cache policy so it passes through the origin's Cache-Control and its minimum TTL does not exceed 300 seconds
- Fix the asset file extension so the computed contentType matches the served one (png/jpg/webp for images, text/html for animations)
- Re-run the publish script once edge propagation completes; verification retries handle transient propagation
Example fix
// before CloudFront policy: Cache-Control override, min TTL 31536000 // after CloudFront policy: origin Cache-Control passthrough, min TTL 0 (<= 300s)
Defensive patterns
Strategy: retry
Validate before calling
const head = await fetch(`${baseUrl}/${key}`, { method: "HEAD" });
const cc = head.headers.get("cache-control") ?? "";
console.assert(head.ok && /max-age=31536000/.test(cc) && /immutable/.test(cc), "asset headers not ready"); Try / catch
try { await verifyAssets(); } catch { await sleep(15_000); /* retry up to ~22 attempts before investigating CDN policy */ } Prevention
- Ensure the CloudFront cache policy forwards origin Cache-Control and has minimum TTL <= 300s
- Use file extensions the script's contentType mapping expects (png/jpg/webp; html for animations)
- Confirm S3 object metadata matches the intended content-type after upload
- Verify with curl -I against the public URL before declaring a publish successful
When it happens
Trigger: CloudFront/S3 serving a different content-type than computed for the asset; CDN cache policy overriding or stripping Cache-Control so max-age=31536000/immutable is absent; an asset not yet propagated or 404 at the edge during all attempts.
Common situations: CloudFront distribution whose default TTL or response-header policy rewrites Cache-Control; S3 metadata for the object differing from the script's contentType mapping (e.g. wrong extension -> image/webp fallback); CDN not yet serving the freshly uploaded keys.
Related errors
- Uploaded, but public verification did not finish. Check
- Artifact download failed: HTTP
- Announcement request failed
- Anthropic Managed Agents request failed with HTTP
- `}
AI-assisted analysis of paperclipai/paperclip@3f1d897a7c (2026-09-18).
Data as JSON: /api/errors/8058553cd8b45a2e.
Report an issue: GitHub.
Appendix: source
Thrown at scripts/publish-announcements.ts:116
delete env.AWS_SESSION_TOKEN;
env.AWS_PROFILE = env.PAPERCLIP_PAGE_AWS_PROFILE;
}
// Only validated files, assets before manifest; credentials are scoped to AWS.
for (const file of prepared.files) execFileSync("aws", announcementUploadArgs(bucket, file), { env, stdio: "pipe" });
console.log("Uploaded. Checking the public manifest (CDN propagation can take five minutes)…");
for (let attempt = 0; attempt < 23; attempt++) {
try {
const response = await fetch(url, { signal: AbortSignal.timeout(10_000), credentials: "omit", redirect: "error" });
const body = announcementManifestSchema.parse(await response.json());
if (response.ok && JSON.stringify(body) === JSON.stringify(prepared.manifest)
&& /(?:^|,)\s*max-age=300(?:\s*,|$)/i.test(response.headers.get("cache-control") ?? "")) {
for (const asset of prepared.files.slice(0, -1)) {
const image = await fetch(`${baseUrl}/${asset.key}`, { method: "HEAD", signal: AbortSignal.timeout(10_000), credentials: "omit", redirect: "error" });
const caching = image.headers.get("cache-control") ?? "";
if (!image.ok || image.headers.get("content-type") !== asset.contentType
|| !/(?:^|,)\s*max-age=31536000(?:\s*,|$)/i.test(caching)
|| !/(?:^|,)\s*immutable(?:\s*,|$)/i.test(caching)) {
throw new Error("Public announcement asset headers are not ready");
}
}
console.log(`Published and verified: ${url}`);
return;
}
} catch { /* Retry edge propagation; uploads have already completed. */ }
if (attempt < 22) await new Promise((resolve) => setTimeout(resolve, 15_000));
}
throw new Error(`Uploaded, but public verification did not finish. Check ${url} and the CloudFront cache policy (minimum TTL must not exceed 300 seconds).`);
}
if (process.argv[1] && path.resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
main().catch((error) => {
console.error(error instanceof Error ? error.message : "Announcement publish failed");
process.exitCode = 1;
});
}
View on GitHub (pinned to 3f1d897a7c)