different-ai/openwork · error

Could not start OpenWork UI control bridge.

Error message

Could not start OpenWork UI control bridge.

What it means

Thrown by `start` in ui-control-server.mjs after the control HTTP server binds to an ephemeral port on 127.0.0.1, if `server.address()` does not yield a usable port. This is a defensive check: a null port means the listening socket did not report an address, so the UI control bridge cannot be advertised in the discovery file and startup aborts.

Solutions

  1. Investigate why the server closed or address() returned null right after a successful listen
  2. Add an error/close listener on the server before listen() to capture the real cause
  3. Retry the start sequence; if persistent, log the address() result for diagnosis

Example fix

// before
uiControlServer.listen(0, '127.0.0.1', () => resolve(undefined));
// after
uiControlServer.on('close', () => console.error('control server closed early'));
uiControlServer.listen(0, '127.0.0.1', () => resolve(undefined));
Defensive patterns

Strategy: retry

Validate before calling

function addressHasPort(address) {
  return typeof address === 'object' && address !== null && typeof address.port === 'number' && address.port > 0;
}

Try / catch

try {
  await startUiControlBridge();
} catch (e) {
  if (e.message === 'Could not start OpenWork UI control bridge.') {
    await retryWithBackoff(startUiControlBridge, 3);
  } else throw e;
}

Prevention

When it happens

Trigger: `uiControlServer.address()` returns null or a non-object (e.g. the server closed between the listen callback and the address read, or an unusual handle/state), making `port` null and tripping the guard.

Common situations: Server closed externally right after startup; IPv6/pipe-shaped address handling assumptions broken; a race where another component tears down the server during init; exotic environments where address() behaves unexpectedly.

Related errors


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

Appendix: source

Thrown at apps/desktop/electron/ui-control-server.mjs:167

          return;
        }
        if (request.method === "POST" && url.pathname === "/execute") {
          sendJsonResponse(response, 200, await runOpenworkControlCommand("execute", await readJsonRequestBody(request)));
          return;
        }
        sendJsonResponse(response, 404, { ok: false, error: "Not found" });
      } catch (error) {
        console.error("[ui-control] request failed", error);
        sendJsonResponse(response, 500, { ok: false, error: "OpenWork UI control request failed." });
      }
    });
    await new Promise((resolve, reject) => {
      uiControlServer.once("error", reject);
      uiControlServer.listen(0, "127.0.0.1", () => resolve(undefined));
    });
    const address = uiControlServer.address();
    const port = typeof address === "object" && address ? address.port : null;
    if (!port) throw new Error("Could not start OpenWork UI control bridge.");
    uiControlDiscoveryPath = path.join(app.getPath("userData"), "openwork-ui-control.json");
    await writeFile(
      uiControlDiscoveryPath,
      `${JSON.stringify({ version: 2, app: appName, identifier: appIdentifier, platform: process.platform, baseUrl: `http://127.0.0.1:${port}`, token: uiControlToken }, null, 2)}\n`,
      "utf8",
    );
    // Make the discovery path available to child processes (server → managed OpenCode → plugin).
    process.env.OPENWORK_UI_CONTROL_DISCOVERY = uiControlDiscoveryPath;
  }

  async function stop() {
    if (uiControlDiscoveryPath) {
      await rm(uiControlDiscoveryPath, { force: true }).catch(() => undefined);
      uiControlDiscoveryPath = null;
    }
    if (!uiControlServer) return;
    await new Promise((resolve) => uiControlServer.close(() => resolve(undefined)));
    uiControlServer = null;

View on GitHub (pinned to 2b7df46e8a)