Embedding OpenClaw Gateway as a Child Process in Electron or Host Apps
This page explains how to supervise the OpenClaw Gateway binary as a child process from Electron or another host application. It covers process ownership, readiness, failure recovery, and upgrades using the Gateway WebSocket protocol.
Read this when
- Embedding OpenClaw in a desktop or server application
- Supervising the Gateway as a child process
- Handling Gateway readiness, restart, shutdown, or invalid config without scraping logs
An embedding host should supervise the running openclaw binary, rely on the Gateway WebSocket protocol for control, and handle the child process as a replaceable runtime. This approach makes process ownership, readiness, failure recovery, and upgrades explicit without depending on OpenClaw's internal state structure.
For client authentication and reconnect state, refer to Building a Gateway client.
Start the child with an embedding preset
Start with a proper node_modules installation and launch the package executable. A solid starting point for a host managing discovery, restarts, and channel lifecycle is:
import { spawn } from "node:child_process";
import { dirname, resolve } from "node:path";
import { fileURLToPath } from "node:url";
// Supply an absolute path to a real Node runtime managed by the host application.
declare const hostNodeExecutable: string;
const packageEntry = fileURLToPath(import.meta.resolve("openclaw"));
const openclawEntry = resolve(dirname(packageEntry), "..", "openclaw.mjs");
const gateway = spawn(hostNodeExecutable, [openclawEntry, "gateway", "--allow-unconfigured"], {
env: {
...process.env,
OPENCLAW_DISABLE_BONJOUR: "1",
OPENCLAW_EXEC_SHELL_SNAPSHOT: "0",
OPENCLAW_NO_RESPAWN: "1",
OPENCLAW_SKIP_CHANNELS: "1",
},
stdio: ["ignore", "inherit", "inherit"],
});
Resolve OpenClaw through the installed package as shown; do not assume a project-local openclaw binary exists on the host process's PATH. The example inherits output so the child process never blocks on full stdout or stderr pipes. If the host captures those streams instead, attach consumers right after spawning.
| Setting | Embedding effect |
|---|---|
OPENCLAW_DISABLE_BONJOUR=1 | Turns off Gateway-managed LAN multicast advertising when the host handles discovery. |
OPENCLAW_NO_RESPAWN=1 | In an unmanaged embedding child, stops OpenClaw from handing an update restart to a detached child. Routine restarts stay in process, so the host retains ownership of the tracked PID. |
OPENCLAW_EXEC_SHELL_SNAPSHOT=0 | Disables login-shell snapshot capture for host exec commands. |
OPENCLAW_SKIP_CHANNELS=1 | Skips channel startup and reload. Set this only when the embedding application wants a control-plane or WebChat-only Gateway. |
--allow-unconfigured bypasses only the gateway.mode=local startup guard. It does not write configuration or fix an invalid file. Leave it out when the embedding application provisions normal local configuration through onboarding, the config CLI, or Gateway RPC.
Electron shell snapshot warning
Shell snapshot capture runs process.execPath -e <script> from a login shell. In a standard Node process, process.execPath is the Node executable. Under Electron, it becomes the Electron binary, which may interpret the call as an application launch and display an "Unable to find Electron app" popup. Set OPENCLAW_EXEC_SHELL_SNAPSHOT=0 in the Gateway child's environment, not just in the renderer process. For the same reason, hostNodeExecutable must point to a real Node runtime instead of Electron's process.execPath.
Handle invalid config by exit code
Gateway startup uses exit code 78 (EX_CONFIG) for configuration-class startup failures, including an invalid config. Branch on the exit code rather than parsing human-readable stderr:
- Run
openclaw doctor --fix --yes --non-interactiveagainst the same config and state environment as the Gateway child. - Retry Gateway startup once after doctor exits successfully.
- If the child exits
78again, stop the repair loop and present the config failure to the user.
Keep stderr for diagnostics, but do not base lifecycle decisions on its text.
After a successful startup, an invalid live config edit is less harmful. The config watcher logs that reload was skipped and continues serving the last accepted in-memory config. Fix the file, then let the watcher accept the next valid snapshot.
Wait for protocol readiness
Use WebSocket signals instead of a log substring:
- Open the Gateway WebSocket.
- Wait for the
connect.challengeevent. It confirms the listener accepted the WebSocket and the challenge handshake can start. - Send
connectwith the challenge-bound device signature. - Treat
hello-okas application readiness for authenticated RPC.
The challenge arrives deliberately earlier than full initialization. If startup sidecars are still pending, connect returns a retryable UNAVAILABLE error with details.reason: "startup-sidecars", a bounded retryAfterMs, and then closes with code 1013 and reason gateway starting. Use resolveGatewayStartupRetryAfterMs from @openclaw/gateway-protocol/startup-unavailable or the reference client's built-in policy, then reconnect.
Interpret restart and shutdown
Before an orderly close, the Gateway broadcasts a shutdown event with reason and restartExpectedMs. A non-null restartExpectedMs means an in-process or supervised restart is expected; null means a terminal shutdown.
The subsequent WebSocket close code is 1012 for both cases. The ordinary client close reason is also service restart in both cases, so neither the close code nor the reason distinguishes restart from shutdown. Preserve the preceding shutdown payload when it arrives, and combine it with the host's own stop intent and the child exit status. If the connection disappears without the event, use normal bounded reconnect and child-supervision policy.
Use RPC instead of state files
Keep the Gateway as the sole owner of OpenClaw state. Common embedding operations already have RPC methods:
| Task | RPC methods |
|---|---|
| Session catalog and lifecycle | sessions.list, sessions.patch, sessions.delete |
| Transcript display | chat.history |
| Cost and usage reports | usage.cost, sessions.usage |
| Model credential status | models.authStatus |
| Configuration | config.get, config.patch |
config.get redacts sensitive values and SecretRef identifiers before returning the snapshot. Write methods also return redacted config. A client must treat the redaction sentinel as opaque and use the documented config write contract; it must never expect the Gateway to return plaintext secrets.
Do not read or mutate files, SQLite tables, transcript files, or cache directories under ~/.openclaw to implement app features. Those layouts are private runtime implementation details and can move or change without protocol compatibility.
Install; do not flatten
The openclaw package at the root level is not meant to be vendored as a single file. Runtime files bundled under dist/extensions still contain bare self-imports, like openclaw/plugin-sdk/*, and the npm package deliberately omits per-extension node_modules trees.
Use npm, pnpm, or any standard Node package manager to install OpenClaw, so Node can properly resolve the package exports and the root dependency tree. Launch the installed openclaw executable. Avoid copying only dist, flattening the package into an application bundle, or vendoring individual extension files.