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.

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)

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.