# The edge cache, and why a green deploy could still ship nothing ## The problem, measured `tools/deploy.sh` step 5 compares `https://partly.party/gutsy/js/boot.js`, fetched the way a browser fetches it, against the bytes just shipped. On 2026-07-26 it failed for the first time and it was right: ``` last-modified: Sun, 26 Jul 2026 12:10:07 GMT cache-control: max-age=14400 cf-cache-status: HIT age: 158 ``` The origin (`forum-nginx`) sends **no `Cache-Control` at all** for `/gutsy/`, so Cloudflare applies its default 4-hour Edge TTL to `.js`. GUTS has no build step and no content hashes in its filenames, so what the edge is holding is not an asset — it is **code**. A deploy lands on the box and players keep running the previous build for up to four hours, with nothing anywhere saying so. Every check above step 5 appends `?v=$BUST`, which is exactly what makes them bypass the edge. So six green checks were compatible with every player being served a stale game. ## The fix (needs one command run by hand) `tools/forum-nginx-default.conf` is the container's config with a `/gutsy/` block added: ```nginx location /gutsy/ { root /usr/share/nginx/html; add_header Cache-Control "no-cache"; } ``` `no-cache` means *store it, but revalidate every time* — the files are small and etag'd, so a revalidation is a 304 and costs nothing. Scoped to `/gutsy/`; no other path on partly.party is affected. Apply it (the copy-into-container step is gated for agents, so this is John's to run): ```bash scp tools/forum-nginx-default.conf humanjing@100.71.119.27:/tmp/default.conf ``` ```bash ssh humanjing@100.71.119.27 "docker cp /tmp/default.conf forum-nginx:/etc/nginx/conf.d/default.conf && docker exec forum-nginx nginx -t && docker exec forum-nginx nginx -s reload" ``` `nginx -t` runs before the reload, so a syntax error refuses to load rather than taking partly.party down. Then confirm — this should report `cf-cache-status: DYNAMIC` or `BYPASS`, and deploy.sh step 5 should pass on the first try after every future deploy: ```bash curl -sI https://partly.party/gutsy/js/boot.js | grep -iE "cf-cache-status|cache-control" ``` ## Until then A deploy is only live to players after the 4-hour TTL expires, or after a manual purge: Cloudflare dashboard → partly.party → Caching → Configuration → Purge Everything. Note the container has **no bind mount** for its nginx config, so a `docker compose up --force-recreate` or an nginx image bump wipes this block. That is why the file lives in the repo: re-applying it is the same two commands.