Repository navigation
Description
Description
When opencode web serves the embedded web UI, responses carry no cache directives at all — no Cache-Control, no ETag, no Last-Modified:
// packages/opencode/src/server/shared/ui.ts (~L55-62)
function embeddedUIResponse(file: string, body: Uint8Array) {
const mime = FSUtil.mimeType(file)
const headers = new Headers({ "content-type": mime })
if (mime.startsWith("text/html")) {
headers.set("content-security-policy", cspForHtml(new TextDecoder().decode(body)))
}
return HttpServerResponse.raw(body, { headers })
}With no explicit policy, cache behavior is left to browser heuristics and any intermediary (reverse proxy, CDN, Cloudflare Tunnel). HTML, the manifest, and entry JS can be served stale after a deploy while the user has no signal that the client is outdated — a different mechanism than the event-stream stall (#39030) but the same user-facing symptom: "the page only shows the new version after Ctrl+F5".
Expected behavior
Explicit, intentional cache policy:
const headers = new Headers({ "content-type": mime })
if (mime.startsWith("text/html")) {
// HTML is the version pivot: always revalidate
headers.set("cache-control", "no-cache")
headers.set("content-security-policy", cspForHtml(new TextDecoder().decode(body)))
} else if (/\.[0-9a-z]{8,}\./.test(file) || file.includes("/assets/")) {
// content-addressed bundle assets only
headers.set("cache-control", "public, max-age=31536000, immutable")
} else {
// manifest, icons, fonts: revalidate cheaply
headers.set("cache-control", "no-cache")
headers.set("etag", etagFor(body)) // cheap content hash
}If assets are content-hashed by the bundler, immutable is safe only for those files; everything else should revalidate. Bonus: emit the server/app version in a header (e.g. X-OpenCode-Version) so a client older than the server can offer a one-click safe reload (preserving prompt drafts).
Steps to reproduce
opencode web, open in a browser.- Upgrade the server and redeploy while the tab is open.
- The open tab keeps running the old UI; depending on intermediary caching, even a normal reload can keep serving stale HTML until a hard refresh.
OpenCode version
v1.18.33 (dev @ 083ed266e)
Related work
| Ref | State | What it reports / fixes |
|---|---|---|
| #41280 | open issue | Bundled web UI version mismatch (package 1.18.15 serves UI 1.18.14) — adjacent version-coherence problem; an explicit X-OpenCode-Version header would let the client detect it. |
| #50398 | open issue | Web UI deployment "reset everything" — deploy-time staleness/state inconsistencies. |
| #48622 | open issue | Request for background auto-update — the version-mismatch affordance proposed here is its UI half. |
| #51860 | open issue | Connection-health surface (filed alongside this) — consumes the version header for its "client outdated" state. |
| #51857 | open issue | SSE stall watchdog regression (filed alongside this) — the other half of "page only updates after Ctrl+F5": this one is deploy-staleness, that one is live-update-staleness. |
Triage note for maintainers
Low-effort, high-leverage hardening: a handful of headers on one response path. Today nothing explicitly says "this HTML is the version pivot — revalidate it", so correctness depends on every intermediary in between (nginx, Cloudflare Tunnel, corporate proxies, AV scanners) doing the right thing by default. Worth batching with #51857 so both staleness mechanisms get fixed in the same release.
Activity
github-actions commentedon Sep 28, 2026
This issue might be a duplicate of an existing issue. Please check:
Cache-Control: no-cacheto HTML responses.afonsoft commentedon Sep 28, 2026
cc @adamdotdevin — small, contained hardening request.
Priority rationale: cheap fix (one response builder,
packages/opencode/src/server/shared/ui.ts) that removes a whole class of deploy-staleness reports, and it pairs naturally with #51857 (live-update staleness) and #51860 (version-mismatch visibility) as a single "web staleness" batch. No behavior-change risk: content-hashed assets getimmutable, everything else just revalidates.neriousy commentedon Oct 19, 2026
v2’s embedded web UI now sends an explicit Cache-Control policy: HTML and service-worker entry files use
no-cache, while other bundled assets use long-lived immutable caching. You can install v2 withnpm i -g @opencode/cli@latest. If you still see this on v2, please open a new issue with the details.