nexu-io/open-design · error · FinalizeUpstreamError
UPSTREAM_UNAVAILABLE
UPSTREAM_UNAVAILABLE
Error message
upstream ${protocol} network error: ${message} What it means
The fetch to the provider threw an error that was neither a FinalizeUpstreamError nor an AbortError — typically TypeError (invalid URL / network failure), ENOTFOUND (DNS), ECONNREFUSED, ECONNRESET, or a TLS handshake failure. It is rewrapped as FinalizeUpstreamError(502) so the route maps it to 502 UPSTREAM_UNAVAILABLE with redacted details.
Source
Thrown at apps/daemon/src/design/finalize-design.ts:396
if (options.apiVersion) callParams.apiVersion = options.apiVersion;
callParams.signal = options.signal
? AbortSignal.any([options.signal, timeoutController.signal])
: timeoutController.signal;
if (options.fetchImpl) callParams.fetchImpl = options.fetchImpl;
try {
response = await callFinalizeProviderWithRetry(callParams);
} catch (err: unknown) {
if (err instanceof FinalizeUpstreamError) throw err;
const errName =
err && typeof err === 'object' && 'name' in err
? (err as { name?: unknown }).name
: '';
if (errName === 'AbortError') throw err; // route handler maps to 503
// Network-level failure (TypeError from fetch, ENOTFOUND/ECONNREFUSED
// via cause.code, etc.) — rewrap as upstream failure so the route
// handler maps to 502 UPSTREAM_FAILED with redacted details.
const message = err instanceof Error ? err.message : String(err);
throw new FinalizeUpstreamError(502, '', `upstream ${protocol} network error: ${message}`);
}
} finally {
clearTimeout(timeoutId);
}
// Phase 8: extract DESIGN.md body and usage counters. A 200 with a body
// that isn't valid JSON (or isn't an object) is treated as an upstream
// failure rather than letting JSON.parse's SyntaxError surface as 500.
let payload: unknown;
try {
payload = await response.json();
} catch (err: unknown) {
const message = err instanceof Error ? err.message : String(err);
throw new FinalizeUpstreamError(
502,
'',
`upstream ${providerLabel(protocol)} returned non-JSON body: ${message}`,
);View on GitHub (pinned to 5be4028344)
Solutions
- Verify the baseUrl is reachable from the daemon host: `curl -i <baseUrl>/v1/messages` (or the provider's health path).
- For Ollama, ensure the server is running on the expected host/port (`ollama serve`).
- Confirm the baseUrl includes the scheme (https:// or http://) and has no trailing path mistakes.
- Check DNS, firewall, and proxy configuration on the daemon host.
Example fix
// before: baseUrl 'anthropic.com' (no scheme -> TypeError) // after: baseUrl 'https://api.anthropic.com'
Defensive patterns
Strategy: validation
Validate before calling
// Verify the provider endpoint is reachable before finalizing.
async function endpointReachable(baseUrl: string): Promise<boolean> {
try {
const res = await fetch(baseUrl, { method: 'HEAD' });
return res.status < 500;
} catch {
return false;
}
}
if (!(await endpointReachable(baseUrl))) {
return res.status(502).json({ error: 'provider endpoint unreachable' });
} Type guard
import { FinalizeUpstreamError } from './finalize-design.js';
function isUpstreamError(err: unknown): err is FinalizeUpstreamError {
return err instanceof FinalizeUpstreamError;
} Try / catch
import { FinalizeUpstreamError } from './finalize-design.js';
try {
await finalizeDesignPackage(db, projectsRoot, dsRoot, projectId, options);
} catch (err) {
if (err instanceof FinalizeUpstreamError && err.status === 502 && err.rawText === '') {
// network-level failure (DNS / connection refused); route maps to 502
}
throw err;
} Prevention
- Always include the URL scheme (https://) in baseUrl.
- For Ollama, confirm `ollama serve` is running on the expected port.
- Check DNS/proxy/firewall on the daemon host before finalizing.
When it happens
Trigger: DNS resolution failure for the provider baseUrl; connection refused (e.g. Ollama not running, wrong port); connection reset mid-request; TLS/cert error; an invalid baseUrl (missing scheme, typo); offline host.
Common situations: Wrong baseUrl (typo, missing https://); Ollama not started locally; the daemon host is offline or behind a blocking corporate proxy; a self-signed cert; the provider endpoint moved; IPv6/IPv4 DNS issues.
Related errors
- Cloudflare returned a non-JSON response.
- Vercel returned a non-JSON response.
- aihubmix image (gemini) ${resp.status}: ${text.slice(0, 240)
- ${tag} ${resp.status}: ${truncate(text, 240)}
- ${providerTag} ${resp.status}: ${truncate(text, 240)}
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/8dc56d5b36b76efd.
Report an issue: GitHub.