anomalyco / anomalyco/opencode
SDK v2 client: text/html interceptor throws on 204 No Content when a front-end (e.g. Cloud Run GFE) injects a default Content-Type, breaking every prompt_async
@rekram1-node is already working on this.
Since Aug 3, 2026.
- Dominant language
- TypeScript
- Stars
- 209k
- Forks
- 27.5k
- PR merge metrics
- PR metrics pending
Description
Description
The v2 client in @opencode-ai/sdk throws on any response whose Content-Type is exactly text/html, regardless of HTTP status code:
// dist/v2/client.js — unchanged from at least 1.17.13 through 1.18.11 (latest as of 2026-08-03)
client.interceptors.response.use((response) => {
const contentType = response.headers.get("content-type");
if (contentType === "text/html")
throw new Error("Request is not supported by this version of OpenCode Server (Server responded with text/html)");
return response;
});
This check exists to catch the server's SPA fallback (200 + index.html) on unmatched routes, which is reasonable. But applying it to 204 No Content responses causes false positives: a 204 has no body, and the engine itself sends 204s (e.g. from POST /session/:id/prompt_async) with no Content-Type header at all. Some HTTP front-ends inject a default Content-Type: text/html (exactly, no charset) into responses that lack one. Google Cloud Run's front-end (GFE) does this — confirmed via browser DevTools showing server: Google Frontend and content-type: text/html on a 204 No Content from prompt_async, while a direct curl against the engine (bypassing the front-end) shows the same 204 with no Content-Type at all.
The result: when opencode serve runs behind Cloud Run (and likely other proxies/CDNs with the same default), every session.promptAsync() call throws this error client-side even though the prompt was accepted and processed successfully (204 = success). Reproduction is deterministic (100% of prompt sends).
Steps to reproduce
The front-end behavior is easy to simulate locally with a ~15-line proxy:
// gfe-sim.mjs — mimics Cloud Run's front-end: inject text/html when Content-Type is missing
import http from "node:http";
const [target, listen] = process.argv.slice(2).map(Number);
http.createServer((req, res) => {
const proxy = http.request(
{ hostname: "127.0.0.1", port: target, path: req.url, method: req.method, headers: req.headers },
(up) => {
const headers = { ...up.headers };
if (!headers["content-type"]) headers["content-type"] = "text/html";
res.writeHead(up.statusCode, headers);
up.pipe(res);
},
);
req.pipe(proxy);
}).listen(listen, "127.0.0.1");
opencode serve --port 4096node gfe-sim.mjs 4096 8099- Point the SDK v2 client at the simulator and send a prompt:
import { createOpencodeClient } from "@opencode-ai/sdk/v2/client";
const client = createOpencodeClient({ baseUrl: "http://127.0.0.1:8099" });
const session = await client.session.create({});
await client.session.promptAsync({ sessionID: session.data.id, parts: [{ type: "text", text: "hello" }] });
// => throws: Request is not supported by this version of OpenCode Server (Server responded with text/html)
The same code pointed directly at :4096 succeeds. The engine's own response (direct curl) is:
HTTP/1.1 204 No Content
Vary: Origin
Date: ...
Content-Length: 0
(no Content-Type), and the prompt runs fine server-side in both cases — only the client-side interceptor fails.
Suggested fix
Exempt 204 from the text/html check (a 204 has no body, so its content-type can never be the SPA fallback this check is guarding against):
- if (contentType === "text/html")
+ if (contentType === "text/html" && response.status !== 204)
throw new Error("Request is not supported by this version of OpenCode Server (Server responded with text/html)");
We've been running exactly this one-line change in a downstream fork of the pre-1.15.11 client since May without issues; after migrating to the published npm SDK we hit this again in production behind Cloud Run.
Environment
@opencode-ai/sdk: 1.17.13 (interceptor confirmed identical through 1.18.11)opencode-ai(engine): 1.17.13- Deployment:
opencode servebehind Google Cloud Run
Workaround
For anyone hitting this behind their own reverse proxy: rewrite Content-Type on 204 responses to application/json (or anything non-text/html) at the proxy layer before the front-end can inject its default. This resolved the failures for us, but it's deployment-specific — the SDK-side exemption above would fix it for everyone.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Assessment
This issue has not been assessed yet.