different-ai/openwork · error · ApiError

opencode_unreachable

opencode_unreachable

Error message

OpenCode engine is unavailable

What it means

The OpenWork server proxies a request to the local OpenCode engine (opencode serve) and the underlying fetch failed with a connection-level failure. `isEngineConnectionFailure` classifies the transport error (ECONNREFUSED, socket close, timeout on connect) and `opencodeUnreachableError` converts it into ApiError code `opencode_unreachable`. This means the server could not reach the engine process backing the configured base URL, not that the route is wrong.

Source

Thrown at apps/server/src/server.ts:1490

      method,
      headers,
      body,
    }).then(() => {
      enginePoolForConfig(input.config)?.reportRequestSuccess(baseUrl);
    }).catch((error: unknown) => {
      if (workspace) enginePoolForConfig(input.config)?.reportRequestFailure(baseUrl, error, workspace);
      // Command failures are surfaced through the OpenCode event stream.
    });
    return jsonResponse({ ok: true, accepted: true });
  }
  const forward = async () => {
    let response: Response;
    try {
      response = await loopbackFetch(targetUrl, { method, headers, body });
      enginePoolForConfig(input.config)?.reportRequestSuccess(baseUrl);
    } catch (error) {
      if (workspace) enginePoolForConfig(input.config)?.reportRequestFailure(baseUrl, error, workspace);
      if (isEngineConnectionFailure(error)) throw opencodeUnreachableError(error, proxyPath);
      throw error;
    }

    if (response.status === 404 && route?.fallback) {
      const fallbackHeaders = headersForEngineConnection(headers, route.fallback);
      let fallbackResponse: Response;
      try {
        fallbackResponse = await loopbackFetch(
          buildOpencodeProxyUrl(route.fallback.baseUrl, proxyPath, search),
          { method, headers: fallbackHeaders, body },
        );
      } catch (error) {
        if (workspace) enginePoolForConfig(input.config)?.reportRequestFailure(route.fallback.baseUrl, error, workspace);
        if (isEngineConnectionFailure(error)) throw opencodeUnreachableError(error, proxyPath);
        throw error;
      }
      return sanitizeProxyResponse(fallbackResponse);
    }

View on GitHub (pinned to 2b7df46e8a)

Solutions

  1. Check the engine process for the config is running (ps / process manager) and restart the server so the engine pool respawns it
  2. Verify the engine baseUrl/port in the config matches the actual opencode serve endpoint (curl the /health or root path)
  3. Inspect server logs for the underlying connection error recorded by reportRequestFailure to confirm ECONNREFUSED vs timeout
  4. If engine startup is slow, add readiness waiting/retry before the first proxy request

Example fix

// before
curl http://localhost:4096/session
// -> connect ECONNREFUSED
// after
opencode serve --port 4096 &
# then restart apps/server so enginePoolForConfig resolves the live baseUrl
curl http://localhost:4096/session
Defensive patterns

Strategy: try-catch

Validate before calling

const res = await fetch(`${baseUrl}/`).catch(() => null);
if (!res) throw new Error("engine not reachable at " + baseUrl);

Type guard

function isOpencodeUnreachable(e: unknown): e is ApiError {
  return e instanceof ApiError && e.code === "opencode_unreachable";
}

Try / catch

try {
  await proxyRequest(req);
} catch (e) {
  if (isOpencodeUnreachable(e)) {
    showEngineDownBanner(); // surface restart guidance instead of raw failure
  } else throw e;
}

Prevention

When it happens

Trigger: A proxied request (method + targetUrl built from the engine baseUrl) throws from loopbackFetch with a connection failure; the pool's reportRequestFailure is also recorded for the workspace. Typical when the engine process crashed, was never started for the current config, or is still booting.

Common situations: OpenCode binary missing or crashed after startup; engine port changed or occupied; config points at a stale baseUrl; container/server started before the engine was ready; firewall or proxy blocking loopback.

Understand the failure class

Background: ECONNREFUSED and "connection refused" / "could not connect to server" errors: what they mean and how to fix them — this error's family across 44 libraries.

Related errors


AI-assisted analysis of different-ai/openwork@2b7df46e8a (2026-09-01). Data as JSON: /api/errors/d865044b213f14d4. Report an issue: GitHub.