Skip to content

Friendly placeholder for deleted GitHub-embedded images (Camo-detected 404) #11

Description

@zachdunn

Idea

When a GitHub-embedded image points at a file that has been deleted, GitHub renders a broken-image icon. For images we can detect are being fetched by GitHub's Camo proxy, we could instead serve a friendly "this image may have been deleted" placeholder so the thread degrades gracefully.

Why this is low priority (deferred deliberately)

  • Cosmetic, rare trigger. The only case this improves is a genuinely-deleted file — uncommon for an internal tool. The payoff is replacing a broken-link icon with a nicer graphic.
  • The expensive part is already solved elsewhere. This started as a bigger investigation because a friendly 404 requires putting code in the request path, which dragged in cache-freshness concerns. Those are now handled at the source by fix(api): set Cache-Control: max-age=60 on uploads so overwrites propagate #10 (Cache-Control: max-age=60 on upload + purge-on-overwrite). So the freshness/caching complexity is out of scope for this — nothing here needs to touch cache behavior.

Design we landed on (if/when we build it)

Serving today: storage.uploads.sh is a direct R2 public custom domain — no Worker in the path — so R2 returns its stock XML 404 and we can't intercept it. Rules/Page Rules/Transform Rules cannot synthesize a placeholder body conditioned on an origin 404, and Custom Error Pages only cover Cloudflare-generated errors. So a friendly image genuinely requires code in the path.

To avoid fronting all reads with a Worker (and the cost/complexity that implies), use an alias domain instead of remapping storage.uploads.sh:

  • New tiny apps/cdn Worker on cdn.uploads.sh, bound to UPLOADS_DEFAULT only (no KV, no auth — the path after the host is the R2 key). Fronts only the shared uploads-default bucket.
  • storage.uploads.sh stays untouched — direct R2, free, zero Worker calls for normal/browser reads.
  • CLI emits the proxy host only for GitHub embeds. The --pr/--issue/--comment flow already knows it's producing a GitHub embed; add a toGithubEmbedUrl(url) helper that swaps host storage.uploads.shcdn.uploads.sh only when the host is storage.uploads.sh (BYO-bucket workspaces on their own publicBaseUrl no-op through it). Apply it in the put markdown, the managed comment body (attachmentsCommentBody), comment, and list --pr paths.

Worker request flow (GET/HEAD):

  • key = path minus leading /; reuse the API's badKey guard.
  • UPLOADS_DEFAULT.get(key)hit: stream with Content-Type (from R2 httpMetadata), ETag, honor If-None-Match304. Cache-Control can just mirror what the API now sets.
  • miss: if User-Agent contains github-camo (case-insensitive) → static placeholder PNG at 200 (PNG, not SVG, for reliable Camo rendering; committed to apps/cdn and bundled so it can't itself 404), short Cache-Control so a later re-upload recovers on its own. Otherwise → plain 404 (matches today's behavior for browsers/hotlinks).

Scope / non-goals

  • Only the shared uploads-default bucket. BYO-bucket GitHub embeds serve from their own domain and won't get the placeholder — acceptable.
  • No Range requests, no dynamic/branded placeholder, no KV/workspace resolution in v1.

Cost note

Only GitHub-embedded reads ever hit the Worker; everything else stays on the free direct path. Even those are Camo-cached, so invocations are low. Workers free tier (100k/day) covers this comfortably.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions