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

  1. Verify the baseUrl is reachable from the daemon host: `curl -i <baseUrl>/v1/messages` (or the provider's health path).
  2. For Ollama, ensure the server is running on the expected host/port (`ollama serve`).
  3. Confirm the baseUrl includes the scheme (https:// or http://) and has no trailing path mistakes.
  4. 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

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


AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12). Data as JSON: /api/errors/8dc56d5b36b76efd. Report an issue: GitHub.