A server block can call setResponseHeader(name, value). It is in the render context and it is in the auto-import list (stx-virtual-ts.js, unresolved-identifiers.js). Under bun dev it works. Under the production server it does nothing, with no warning.
Where the two paths diverge
render.js records the header onto the context:
setResponseHeader: (name, value) => {
if (!name) return
const headers = renderOptions.context?.__stxResponseHeaders || {}
headers[String(name)] = String(value)
recordOn(renderOptions.context, '__stxResponseHeaders', headers)
}@stacksjs/bun-router reads it back and merges it into the response:
let AY = Number(F.__stxResponseStatus), g8 = F.__stxResponseHeaders
return { html: y8, status: ..., headers: { ...z.headers, ...(g8 ?? {}) } }production-server.js never mentions it:
$ grep -c __stxResponseHeaders node_modules/@stacksjs/stx/dist/production-server.js
0hydrateTemplateStream returns { html, boundaries, redirect, cookies }, and the production server builds every response from those four fields. There is no channel for __stxResponseHeaders, so the object is populated and then dropped on the floor.
Why it matters
This is the API you reach for the moment you need any response header a template does not otherwise control: Cache-Control on a page whose freshness depends on what the server block just read, Content-Language, Vary, X-Robots-Tag on a page that turned out to be noindex, Link preload hints computed from the data.
Every one of those is correct in dev and silently absent in production, which is the worst direction for a divergence to run: it ships. A header that is merely missing does not throw, does not log, and does not show up in any test that renders HTML rather than inspecting a Response.
setResponseStatus and notFound are recorded through the same recordOn mechanism. production-server.js does not read __stxResponseStatus either, so the same question applies to them.
Suggested fix
Return the recorded headers from runHydration the way cookies is already returned (stx#1927 added exactly that channel for Set-Cookie, so the plumbing shape exists), and merge them in production-server.js on both the redirect and the HTML paths.
Failing that, the honest alternative is to drop setResponseHeader from the auto-import surface so it cannot be reached from a server block at all. Silently working in one environment is worse than not existing.
Environment
@stacksjs/stx 0.2.198, bun 1.3.14, macOS 15, stacks@0.72.34.