{"record":{"id":"d865044b213f14d4","repo":"different-ai/openwork","slug":"opencode-unreachable","errorCode":"opencode_unreachable","errorMessage":"OpenCode engine is unavailable","messagePattern":"OpenCode engine is unavailable","errorType":"http","errorClass":"ApiError","httpStatus":502,"severity":"error","filePath":"apps/server/src/server.ts","lineNumber":1490,"sourceCode":"      method,\n      headers,\n      body,\n    }).then(() => {\n      enginePoolForConfig(input.config)?.reportRequestSuccess(baseUrl);\n    }).catch((error: unknown) => {\n      if (workspace) enginePoolForConfig(input.config)?.reportRequestFailure(baseUrl, error, workspace);\n      // Command failures are surfaced through the OpenCode event stream.\n    });\n    return jsonResponse({ ok: true, accepted: true });\n  }\n  const forward = async () => {\n    let response: Response;\n    try {\n      response = await loopbackFetch(targetUrl, { method, headers, body });\n      enginePoolForConfig(input.config)?.reportRequestSuccess(baseUrl);\n    } catch (error) {\n      if (workspace) enginePoolForConfig(input.config)?.reportRequestFailure(baseUrl, error, workspace);\n      if (isEngineConnectionFailure(error)) throw opencodeUnreachableError(error, proxyPath);\n      throw error;\n    }\n\n    if (response.status === 404 && route?.fallback) {\n      const fallbackHeaders = headersForEngineConnection(headers, route.fallback);\n      let fallbackResponse: Response;\n      try {\n        fallbackResponse = await loopbackFetch(\n          buildOpencodeProxyUrl(route.fallback.baseUrl, proxyPath, search),\n          { method, headers: fallbackHeaders, body },\n        );\n      } catch (error) {\n        if (workspace) enginePoolForConfig(input.config)?.reportRequestFailure(route.fallback.baseUrl, error, workspace);\n        if (isEngineConnectionFailure(error)) throw opencodeUnreachableError(error, proxyPath);\n        throw error;\n      }\n      return sanitizeProxyResponse(fallbackResponse);\n    }","sourceCodeStart":1472,"sourceCodeEnd":1508,"githubUrl":"https://github.com/different-ai/openwork/blob/2b7df46e8ae1517d64c896c7793d2d52ec845669/apps/server/src/server.ts#L1472-L1508","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Check the engine process for the config is running (ps / process manager) and restart the server so the engine pool respawns it","Verify the engine baseUrl/port in the config matches the actual opencode serve endpoint (curl the /health or root path)","Inspect server logs for the underlying connection error recorded by reportRequestFailure to confirm ECONNREFUSED vs timeout","If engine startup is slow, add readiness waiting/retry before the first proxy request"],"exampleFix":"// before\ncurl http://localhost:4096/session\n// -> connect ECONNREFUSED\n// after\nopencode serve --port 4096 &\n# then restart apps/server so enginePoolForConfig resolves the live baseUrl\ncurl http://localhost:4096/session","handlingStrategy":"try-catch","validationCode":"const res = await fetch(`${baseUrl}/`).catch(() => null);\nif (!res) throw new Error(\"engine not reachable at \" + baseUrl);","typeGuard":"function isOpencodeUnreachable(e: unknown): e is ApiError {\n  return e instanceof ApiError && e.code === \"opencode_unreachable\";\n}","tryCatchPattern":"try {\n  await proxyRequest(req);\n} catch (e) {\n  if (isOpencodeUnreachable(e)) {\n    showEngineDownBanner(); // surface restart guidance instead of raw failure\n  } else throw e;\n}","preventionTips":["Run the engine under a process supervisor with automatic restart","Health-check the engine baseUrl before routing traffic","Pin engine port in config and verify it on startup","Monitor enginePool reportRequestFailure signals with alerts"],"tags":["network","proxy","engine","process-lifecycle"],"backgroundTag":"connection-refused","analyzedSha":"2b7df46e8ae1517d64c896c7793d2d52ec845669","analyzedAt":"2026-09-01T07:59:23.713Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}