Skip to content

stale-page-not-broken-server

"The GUI isn't rendering" had five suspects; a private browser tab acquitted four of them in one experiment — the phone was faithfully rendering a corpse it had cached weeks earlier.

The debugging arc (2026-07-08)

Report: "GUI not rendering" on phone Safari over Tailscale. Suspects: server down, code broken, Tailscale routing, page bug, phone-side state. Evidence gathered in order:

  1. /healthz returned ok with the right sha → server up, right checkout.
  2. Headless Chromium loaded the exact tailnet URL the phone uses: page rendered, zero console errors, all requests 200 → not the code, not the route.
  3. That leaves phone-side state. Discriminating experiment: open the same URL in a private Safari tab (same phone, same network, no cache). Verdict from the maintainer: "It works."

Root cause: Safari had cached a dead page — saved while a defunct static server briefly occupied the bookmark's port — and heuristic caching kept serving it. Every layer below the browser was healthy.

The fixes, one per layer

  • Never let it recur: the launcher HTML is served with Cache-Control: no-store (_engine/server.py), pinned by a test — the page changes every slice, so no copy of it should ever be cacheable.
  • Make it diagnosable from the phone: /healthz reports host, git sha, uptime, keeper heartbeat, provider availability, and the page footer renders it. "Is it down, or is it me?" is now answerable without a desktop.

The transferable lesson

When a client reports breakage that the server-side evidence contradicts, the cheapest discriminating experiment is a cache-free client (private tab, curl, different device) — it splits the hypothesis space into "server/network" vs "client state" in one move. And any page that changes on every deploy should say what it is (sha in the footer) so staleness is visible instead of invisible.