Bun 1.4.3 ERR_PROXY_TUNNEL: is it a Sume error or your proxy?
Bun 1.4.3 rejects fetch with ERR_PROXY_TUNNEL on a failed CONNECT. A Sume error always carries error.request_id; use it to tell the two apart.

If a Bun fetch to api.sume.com rejects with ERR_PROXY_TUNNEL, the failure happened at your proxy, not at Sume. Sume's own errors always come back as a JSON body with error.code and error.request_id, so the presence of a request_id is the cheapest way to separate a Sume refusal from a network-path failure.
What Bun 1.4.3 changed
The Bun v1.4.3 release notes (read 2026-10-10) say a non-2xx response to a proxy CONNECT now rejects fetch with ERR_PROXY_TUNNEL, carrying the status and headers. They also say NO_PROXY now accepts wildcards, CIDR blocks, bare IPv6 and host:port.
| Behavior | Before / after |
|---|---|
| Non-2xx CONNECT response | Rejects with ERR_PROXY_TUNNEL, with status and headers attached |
| NO_PROXY syntax | Accepts wildcards, CIDR blocks, bare IPv6, host:port |
What a Sume error looks like
The API reference documents one error envelope with the request id inside error, and the errors page lists the codes. A proxy that refuses the tunnel never reaches Sume, so no envelope exists.
| What you see | Source | Safe next step |
|---|---|---|
| JSON with error.code and error.request_id | Sume | Branch on code: 402 add funds, 429 back off with retry-after, 400 fix the request |
| Rejection with no response, such as ERR_PROXY_TUNNEL | Your network path | Fix proxy or NO_PROXY. If you sent a paid submit, retry only with the same Idempotency-Key |
| HTML or non-Sume JSON body with an HTTP status | Gateway or proxy in between | Treat as transport; do not read it as a Sume code |
A classifier you can paste
This wrapper returns one of three kinds. It only trusts a response as Sume's when error.request_id is present.
export async function sumeFetch(url, init) {
let res;
try {
res = await fetch(url, init);
} catch (e) {
return { kind: "transport", code: e.code ?? e.name };
}
const body = await res.json().catch(() => null);
const err = body?.error;
if (err?.request_id) {
return { kind: "sume", status: res.status, code: err.code, requestId: err.request_id };
}
return { kind: "intermediary", status: res.status };
}Retry rule for paid submits
A transport failure on a submit leaves you not knowing whether the job was created. Sume's docs say to send an Idempotency-Key on paid submits so a retry returns the original job instead of billing a second one. Reuse the same key for the exact retry, and use a new key only for new work.
Sources
Related posts
More in Developers
- Bun 1.4.3 fake timers: test a Sume poll loop with request timeouts
Bun 1.4.3 fixes advanceTimersByTime spinning with AbortSignal.timeout. Test a Sume job poll loop that honors next_poll_after_seconds without real waits.
- Bun 1.4.3 fetch Content-Length check: send a Sume JSON body as bytes
Bun 1.4.3 rejects fetch when a declared Content-Length mismatches a stream body. For a Sume submit, skip the header and pass the encoded JSON.
- bun run --check: type errors stop before a Sume job submits
Bun 1.4.3 adds bun check and a --check flag on bun run. Use it so a type error in your Sume script fails before a paid generation request is sent.
- Can you cancel an image generation on Sume? Only before it starts
Cancelling a Sume image job works only before generation work starts. After that the API returns 409 job_generation_already_started and the job runs and bills.
Written by Sume