OpenClaw Gateway Configuration Reference: Keys, Defaults, and Schema

Field-level reference for openclaw.json keys, defaults, and links to subsystem pages. For task-oriented setup, see the configuration guide.

Read this when

  • You need exact field-level config semantics or defaults
  • You are validating channel, model, gateway, or tool config blocks

Field-level reference for ~/.openclaw/openclaw.json: keys, defaults, and links to deeper subsystem pages. For task-oriented setup guidance, see Configuration. Channel- and plugin-owned command catalogs and deep memory knobs live on their own pages, not here.

Config format is JSON5 (comments + trailing commas allowed). All fields are optional; OpenClaw uses safe defaults when omitted.

Code truth beats this page:

  • openclaw config schema prints the live JSON Schema used for validation and Control UI, with bundled/plugin/channel metadata merged in.
  • Agents should call the gateway tool action config.schema.lookup for one exact path-scoped schema node before editing config.
  • pnpm config:docs:check / pnpm config:docs:gen validate this doc's baseline hash against the current schema surface.

Schema uiHints also carry a resolved advanced boolean for every path. Control UI uses it to show common fields first and collapse advanced fields per section; search still spans both tiers. Tier metadata is presentational only. When adding a key, declare its tier on the leaf or let it inherit the nearest ancestor declaration. A path with no declared ancestor is advanced by default.

Dedicated deep references:

  • Memory configuration reference for memory.search.*, memory.citations, and dreaming config under plugins.entries.memory-core.config.dreaming.
  • Slash commands for the current built-in + bundled command catalog.
  • Owning channel/plugin pages for channel-specific command surfaces.

Channels

Per-channel config keys live in Configuration - channels: channels.* for Slack, Discord, Telegram, WhatsApp, Matrix, iMessage, and other channel plugins (auth, access control, multi-account, mention gating).

Agent defaults, multi-agent, sessions, and messages

See Configuration - agents for:

  • agents.defaults.* (workspace, model, thinking, heartbeat, memory, media, skills, sandbox)
  • multiAgent.* (multi-agent routing and bindings)
  • session.* (session lifecycle, compaction, pruning)
  • messages.* (message delivery, TTS, markdown rendering)
  • talk.* (Talk mode)
    • talk.consultThinkingLevel: thinking level override for the full OpenClaw agent run behind Control UI Talk realtime consults
    • talk.consultFastMode: one-shot fast-mode override for Control UI Talk realtime consults
    • talk.speechLocale: optional BCP 47 locale id for Talk speech recognition on Android, iOS, and macOS, and for iOS system-voice fallback
    • talk.silenceTimeoutMs: when unset, Talk keeps the platform default pause window before sending the transcript (700 ms on macOS and Android, 900 ms on iOS)
    • talk.realtime.consultRouting: Gateway relay fallback for finalized realtime Talk transcripts that skip openclaw_agent_consult

Tools and custom providers

Tool policy, experimental toggles, provider-backed tool config, and custom provider / base-URL setup live in Configuration - tools and custom providers.

Models

Provider definitions, model allowlists, and custom provider setup live in Configuration - tools and custom providers. The models root also owns global model-catalog behavior.

{
  models: {
    // Optional. Hosted catalog updates default on.
    catalogRefresh: {
      enabled: true,
      // url: "https://catalog.example.com/openclaw/catalog.json",
    },
  },
}
  • models.mode: provider catalog behavior (merge or replace).
  • models.providers: custom provider map keyed by provider id.
  • models.providers.*.localService: optional on-demand process manager for local model servers. OpenClaw probes the configured health endpoint, starts the absolute command when needed, waits for readiness, then sends the model request. See Local model services.
  • models.catalogRefresh.enabled: controls the hosted model catalog refresh (default: true). Set it to false to prevent all remote catalog requests; model metadata and pricing then stay at the values shipped in the installed release or declared under models.providers.*.models[].cost.
  • models.catalogRefresh.url: optional HTTPS mirror override (plain HTTP is accepted only for explicit localhost testing). The Gateway checks in the background at startup and every six hours. A downloaded catalog applies on the next Gateway restart; a release whose bundled catalog is newer always wins.

Pricing updates ship in the same hosted catalog file as model metadata. The retired models.pricing toggle is removed automatically by openclaw doctor --fix; use models.catalogRefresh.enabled: false when OpenClaw must avoid all hosted catalog traffic.

MCP

OpenClaw-managed MCP server definitions live under mcp.servers and are consumed by embedded OpenClaw and other runtime adapters. The openclaw mcp list, show, set, and unset commands manage this block without connecting to the target server during config edits.

{
  mcp: {
    servers: {
      docs: {
        command: "npx",
        args: ["-y", "@modelcontextprotocol/server-fetch"],
      },
      remote: {
        url: "https://example.com/mcp",
        transport: "streamable-http", // streamable-http | sse
        requestTimeoutMs: 20000,
        connectionTimeoutMs: 5000,
        supportsParallelToolCalls: true,
        headers: {
          Authorization: "Bearer ${MCP_REMOTE_TOKEN}",
        },
        auth: "oauth",
        oauth: {
          identity: "per-requester", // shared | per-requester; default: shared
          scope: "docs.read",
        },
        sslVerify: true,
        clientCert: "/path/to/client.crt",
        clientKey: "/path/to/client.key",
        toolFilter: {
          include: ["search_*"],
          exclude: ["admin_*"],
        },
        // Optional Codex app-server projection controls.
        codex: {
          agents: ["main"],
          defaultToolsApprovalMode: "approve", // auto | prompt | approve
        },
      },
    },
  },
}
  • mcp.servers: named stdio or remote MCP server definitions for runtimes that expose configured MCP tools. Remote entries use transport: "streamable-http" or transport: "sse"; type: "http" is a CLI-native alias that openclaw mcp set and openclaw doctor --fix normalize into the canonical transport field.
  • mcp.servers.<name>.enabled: set false to keep a saved server definition while excluding it from embedded OpenClaw MCP discovery and tool projection.
  • mcp.servers.<name>.requestTimeoutMs: per-server MCP request timeout in milliseconds.
  • mcp.servers.<name>.connectionTimeoutMs: per-server connection timeout in milliseconds.
  • mcp.servers.<name>.supportsParallelToolCalls: optional concurrency hint for adapters that can choose whether to issue parallel MCP tool calls.
  • mcp.servers.<name>.auth: set "oauth" for HTTP MCP servers that require OAuth. Run openclaw mcp login <name> to store tokens under OpenClaw state.
  • mcp.servers.<name>.oauth: optional OAuth scope, redirect URL, and client metadata URL overrides.
  • mcp.servers.<name>.oauth.identity: credential ownership. Omit it or set "shared" for operator-managed credentials; set "per-requester" to isolate credentials for each authenticated sender. Per-requester OAuth requires an HTTP server URL, cannot use oauth.authProfileId, and requires gateway.publicOrigin for its callback.
  • mcp.servers.<name>.sslVerify, clientCert, clientKey: HTTP TLS controls for private endpoints and mutual TLS.
  • mcp.servers.<name>.toolFilter: optional per-server tool selection. include limits the discovered MCP tools to matching names; exclude hides matching names. Entries are exact MCP tool names or simple * globs. Servers with resources or prompts also generate utility tool names (resources_list, resources_read, prompts_list, prompts_get), and those names use the same filter.
  • mcp.servers.<name>.codex: optional Codex app-server projection controls. This block is OpenClaw metadata for Codex app-server threads only; it does not affect ACP sessions, generic Codex harness config, or other runtime adapters. Non-empty codex.agents limits the server to the listed OpenClaw agent ids. Empty, blank, or invalid scoped agent lists are rejected by config validation and omitted by the runtime projection path instead of becoming global. codex.defaultToolsApprovalMode emits Codex's native default_tools_approval_mode for that server. OpenClaw strips the codex block before passing native mcp_servers config to Codex. Omit the block to keep the server projected for every Codex app-server agent with Codex's default MCP approval behavior.
  • Session-scoped bundled MCP runtimes use a built-in 10-minute idle TTL. One-shot embedded runs request run-end cleanup; the TTL is the backstop for long-lived sessions and future callers.
  • Changes under mcp.* hot-apply by disposing cached session MCP runtimes. The next tool discovery/use recreates them from the new config, so removed mcp.servers entries are reaped immediately instead of waiting for idle TTL.
  • Runtime discovery also honors MCP tool-list change notifications by dropping the cached catalog for that session. Servers that advertise resources or prompts get utility tools for listing/reading resources and listing/fetching prompts. Repeated tool-call failures pause the affected server briefly before another call is attempted.

See MCP and CLI backends for runtime behavior.

Skills

{
  skills: {
    allowBundled: ["gemini", "peekaboo"],
    load: {
      extraDirs: ["~/Projects/agent-scripts/skills"],
      allowSymlinkTargets: ["~/Projects/manager/skills"],
    },
    install: {
      preferBrew: true,
      nodeManager: "npm", // npm | pnpm | yarn | bun
      allowUploadedArchives: false,
    },
    workshop: {
      allowSymlinkTargetWrites: false,
    },
    entries: {
      "image-lab": {
        apiKey: { source: "env", provider: "default", id: "GEMINI_API_KEY" }, // or plaintext string
        env: { GEMINI_API_KEY: "GEMINI_KEY_HERE" },
      },
      peekaboo: { enabled: true },
      sag: { enabled: false },
    },
  },
}
  • allowBundled: optional allowlist that applies exclusively to bundled skills; managed or workspace skills are not affected.
  • load.extraDirs: additional shared skill roots, given the lowest precedence.
  • load.allowSymlinkTargets: trusted real target roots that skill symlinks may resolve into when the link is located outside its configured source root.
  • workshop.allowSymlinkTargetWrites: permits Skill Workshop apply to write through symlink targets that are already trusted (default: false).
  • install.preferBrew: when set to true, Homebrew installers are preferred if brew is present, before other installer kinds are tried.
  • install.nodeManager: node installer preference for metadata.openclaw.install specs (npm | pnpm | yarn | bun).
  • install.allowUploadedArchives: lets trusted operator.admin Gateway clients install private zip archives staged via skills.upload.* (default: false). This only activates the uploaded-archive path; standard ClawHub installs do not need it.
  • entries.<skillKey>.enabled: false: turns off a skill even when it is bundled or installed.
  • entries.<skillKey>.apiKey: a convenience for skills that declare a primary env var (either a plaintext string or a SecretRef object).
  • limits.maxCandidatesPerRoot, limits.maxSkillsLoadedPerSource, limits.maxSkillsInPrompt, limits.maxSkillsPromptChars, limits.maxSkillFileBytes: govern skill discovery and the model-facing skills prompt.
  • Skill Workshop autonomy and approval settings (workshop.autonomous.mode, workshop.approvalPolicy, workshop.maxPending, workshop.maxSkillBytes) are covered in Skills configuration.

Plugins

{
  plugins: {
    enabled: true,
    allow: ["voice-call"],
    deny: [],
    load: {
      paths: ["~/Projects/oss/voice-call-plugin"],
    },
    entries: {
      "voice-call": {
        enabled: true,
        hooks: {
          allowPromptInjection: false,
        },
        config: { provider: "twilio" },
      },
    },
  },
}
  • Plugin loading pulls from package or bundle directories referenced by ~/.openclaw/extensions and <workspace>/.openclaw/extensions, as well as any files or folders named in plugins.load.paths.
  • Standalone plugin files belong in plugins.load.paths; extension roots found automatically skip top-level .js, .mjs, and .ts files, so helper scripts located there won't prevent startup.
  • The discovery process recognizes native OpenClaw plugins, compatible Codex bundles, and Claude bundles, including Claude default-layout bundles without a manifest.
  • A gateway restart is mandatory after any config change.
  • allow: an optional allowlist that loads only the plugins it lists. deny takes precedence.
  • plugins.entries.<id>.apiKey: a convenience field for plugin-level API keys, usable when the plugin supports it.
  • plugins.entries.<id>.env: a map of environment variables scoped to the plugin.
  • plugins.entries.<id>.hooks.allowPromptInjection: when false is set, core blocks prompt-modifying hooks like before_prompt_build. This applies to native plugin hooks and to hook directories supplied by supported bundles.
  • plugins.entries.<id>.hooks.allowConversationAccess: when true is set, trusted non-bundled plugins can access raw conversation content through typed hooks including before_model_resolve, agent_turn_prepare, before_prompt_build, before_agent_reply, llm_input, llm_output, before_agent_run, before_agent_finalize, and agent_end.
  • plugins.entries.<id>.subagent.allowModelOverride: marks this plugin as trusted to request per-run provider and model overrides for background subagent executions.
  • plugins.entries.<id>.subagent.allowedModels: an optional allowlist of canonical provider/model targets for trusted subagent overrides. Reserve "*" for cases where you deliberately permit any model.
  • plugins.entries.<id>.llm.allowModelOverride: marks this plugin as trusted to request model overrides for api.runtime.llm.complete.
  • plugins.entries.<id>.llm.allowedModels: an optional allowlist of canonical provider/model targets for trusted model overrides. Use "*" only when allowing any model override is intentional.
  • plugins.entries.<id>.llm.allowedCompletionModels: an optional allowlist applied to every plugin LLM completion, covering host-resolved defaults and overrides. Choose "*" only when you intentionally want to permit any model.
  • plugins.entries.<id>.llm.allowAuthProfileOverride: marks this plugin as trusted to pick a non-default auth profile for isolated api.runtime.llm.complete runs. Direct model@profile calls stay under model-override policy.
  • plugins.entries.<id>.llm.allowAgentIdOverride: marks this plugin as trusted to execute api.runtime.llm.complete with a non-default agent id.
  • plugins.entries.<id>.config: a plugin-defined config object, validated by the native OpenClaw plugin schema when one exists.
  • Channel plugin account and runtime settings reside under channels.<id> and should be documented via the owning plugin's manifest channelConfigs metadata, rather than a central OpenClaw option registry.

Codex harness plugin config

The bundled codex plugin owns native Codex app-server harness settings under plugins.entries.codex.config. The complete config surface is in the Codex harness reference and the runtime model is covered by Codex harness.

codexPlugins applies only to sessions that pick the native Codex harness. It does not activate Codex plugins for OpenClaw provider runs, ACP conversation bindings, or any harness other than Codex.

{
  plugins: {
    entries: {
      codex: {
        enabled: true,
        config: {
          codexPlugins: {
            enabled: true,
            allow_all_plugins: true,
            allow_destructive_actions: "auto",
            plugins: {
              "google-calendar": {
                enabled: true,
                marketplaceName: "openai-curated",
                pluginName: "google-calendar",
                allow_destructive_actions: false,
              },
            },
          },
        },
      },
    },
  },
}
  • plugins.entries.codex.config.codexPlugins.enabled: turns on native Codex plugin/app support for the Codex harness. Default: false.
  • plugins.entries.codex.config.codexPlugins.allow_all_plugins: surfaces all currently reachable apps tied to the authenticated Codex account in every fresh native Codex thread. Default: false.
  • plugins.entries.codex.config.codexPlugins.allow_destructive_actions: the default policy for destructive actions when a configured plugin app elicits one. Choose true to permit safe Codex approval schemas without asking, false to reject them, "auto" to forward Codex-required approvals to OpenClaw plugin approvals, or "ask" to ask for each plugin write/destructive action without saving approval. The "ask" mode removes durable Codex per-tool approval overrides for the app in question and assigns the human approvals reviewer to that app before the Codex thread begins. Default: true.
  • plugins.entries.codex.config.codexPlugins.plugins.<key>.enabled: activates a configured plugin entry when global codexPlugins.enabled is also enabled. Default: true for explicit entries.
  • plugins.entries.codex.config.codexPlugins.plugins.<key>.marketplaceName: a fixed marketplace identifier, needed alongside pluginName for every resolved entry. Any valid marketplace Codex can already discover is accepted, including "openai-curated", "openai-bundled", "openai-primary-runtime", "workspace-directory", and repository-local marketplace identities. Entries lacking either identity field are skipped.
  • plugins.entries.codex.config.codexPlugins.plugins.<key>.pluginName: a fixed Codex plugin identifier, needed alongside marketplaceName. For marketplaces whose plugin identifiers are marketplace-qualified, use the exact identity Codex reports. /codex plugins available shows discoverable identities, and an owner or operator.admin can install one via /codex plugins install <plugin>@<marketplace>.
  • plugins.entries.codex.config.codexPlugins.plugins.<key>.allow_destructive_actions: a per-plugin destructive-action override. If omitted, the global allow_destructive_actions value applies. The per-plugin value supports the same true, false, "auto", or "ask" policies.

For any admitted plugin app that relies on "ask", that app's approval requests go to the human reviewer. Other apps and non-app thread approvals keep their assigned reviewer, so mixed plugin policies do not adopt "ask" behavior.

codexPlugins.enabled serves as the global enablement directive. Explicit plugin entries written by migration preserve durable curated install and repair eligibility. An owner or operator.admin can add other discovered plugins with /codex plugins install <plugin>@<marketplace>; Codex still manages upstream installation and connector authentication. Plugins without exact identity, installation, or accessible app ownership fail closed. plugins["*"] is not supported, and local marketplacePath values are intentionally not config fields because they are host-specific. See Native Codex plugins for app-server version and readiness requirements.

app/installed readiness checks (with authorized metadata from batched app/read) are cached for one hour and refreshed asynchronously when stale. Codex thread app config is computed at Codex harness session establishment, not on every turn; use /new, /reset, or a gateway restart after changing native plugin config.

codexPlugins.allow_all_plugins snapshots every currently accessible account app into each new native Codex thread. It does not install plugins or apps, and inaccessible apps stay excluded. Account apps use the global codexPlugins.allow_destructive_actions policy. Explicit plugin entries take precedence when the same app is present in both paths. If app/installed cannot be read, account-wide exposure fails closed.

  • plugins.entries.firecrawl.config.webFetch: Configuration for the Firecrawl web-fetch provider.
    • apiKey: An optional Firecrawl API key to raise rate limits (supports SecretRef). When absent, it falls back to the plugins.entries.firecrawl.config.webSearch.apiKey or FIRECRAWL_API_KEY environment variable.
    • baseUrl: The base URL for the Firecrawl API (defaults to https://api.firecrawl.dev; if self-hosted, it must point to private or internal endpoints).
    • onlyMainContent: Pull only the primary content from pages (default: true).
    • maxAgeMs: The maximum cache age, expressed in milliseconds (default: 172800000, which equals 2 days).
    • timeoutSeconds: The timeout for scrape requests, in seconds (default: 60).
  • plugins.entries.xai.config.xSearch: Settings for xAI X Search, also known as Grok web search.
    • enabled: Turns on the X Search provider.
    • model: The Grok model used for search queries (for instance, "grok-4.3").
  • plugins.entries.memory-core.config.dreaming: Settings for memory dreaming. Refer to Dreaming for phase details and thresholds.
    • enabled: The master switch for dreaming (defaults to false).
    • frequency: The cron schedule for each complete dreaming cycle ("0 3 * * *" is the default).
    • model: An optional model override for the Dream Diary subagent. This requires plugins.entries.memory-core.subagent.allowModelOverride: true; combine it with allowedModels to limit targets. If the model is unavailable, the system retries once using the session's default model; trust or allowlist failures never fall back without notice.
    • Phase policies and thresholds are internal implementation specifics, not user-facing configuration keys.
  • The complete memory configuration is documented in Memory configuration reference:
    • memory.search.*
    • agents.entries.*.memory.search.* for overrides on a per-agent basis
    • memory.citations
    • plugins.entries.memory-core.config.dreaming
  • Enabled Claude bundle plugins may also supply embedded OpenClaw defaults from settings.json; OpenClaw applies these as sanitized agent settings rather than as direct OpenClaw configuration patches.
  • plugins.slots.memory: Choose the active memory plugin ID, or use "none" to turn off memory plugins.
  • plugins.slots.contextEngine: Choose the active context engine plugin ID; unless you install and pick a different engine, it defaults to "legacy".

See Plugins.


Browser

{
  browser: {
    enabled: true,
    evaluateEnabled: true,
    defaultProfile: "user",
    ssrfPolicy: {
      // dangerouslyAllowPrivateNetwork: true, // opt in only for trusted private-network access
      // allowPrivateNetwork: true, // legacy alias
      // allowedHostnames: ["*.example.com", "example.com", "localhost"],
    },
    tabCleanup: {
      enabled: true,
    },
    extensionRelay: {
      allowLegacyAuth: true,
    },
    profiles: {
      openclaw: { cdpPort: 18800 },
      work: {
        cdpPort: 18801,
        executablePath: "/Applications/Google Chrome.app/Contents/MacOS/Google Chrome",
      },
      chrome: { driver: "extension" },
      user: { driver: "existing-session", attachOnly: true },
      brave: {
        driver: "existing-session",
        attachOnly: true,
        userDataDir: "~/Library/Application Support/BraveSoftware/Brave-Browser",
      },
      remote: { cdpUrl: "http://10.0.0.42:9222" },
    },
    // headless: false,
    // noSandbox: false,
    // extraArgs: [],
    // executablePath: "/Applications/Brave Browser.app/Contents/MacOS/Brave Browser",
    // attachOnly: false,
  },
}
  • Setting evaluateEnabled: false turns off both act:evaluate and wait --fn.
  • For a single Browser Relay Authentication migration window, extensionRelay.allowLegacyAuth comes preset to true. During that period, older extension and external CDP clients using Bearer, Basic, or token-subprotocol auth remain supported. Once every relay client has moved to auth v2, switch it to false; v2 clients never fall back to older auth.
  • Best-effort periodic cleanup for tracked primary-agent tabs, whether after idle time or once a session goes past its cap, is governed by tabCleanup. Only tabs spawned by browser tool action: "open" are tracked; user-opened tabs or those with unknown ownership are never picked up. Turning off tabCleanup leaves explicit session lifecycle cleanup unaffected.
  • Host-local opens that come with a stable native CDP target and browser identity are kept in shared SQLite state, so they stay eligible for /new and session lifecycle cleanup even after Gateway restarts. Native tool-facing CDP targets likewise remain candidates for idle and cap cleanup post-restart. Since Chrome MCP works with process-local target handles, cold existing-session records wait for lifecycle cleanup instead of risking an idle sweep against unattributable post-restart activity. OpenClaw checks the profile and browser instance before closing. Chrome MCP auto-connect, a missing /json/version browser identity, and unresolved native targets all stay process-local, so they are not auto-closed after a restart. Older untracked tabs need manual closing. Transient failures remain pending for a later retry. Refer to Tab cleanup ownership.
  • With ssrfPolicy.dangerouslyAllowPrivateNetwork left unset, it is disabled, so browser navigation stays strict by default.
  • Only set ssrfPolicy.dangerouslyAllowPrivateNetwork: true when you deliberately trust private-network browser navigation.
  • In strict mode, remote CDP profile endpoints (profiles.*.cdpUrl) face the same private-network blocking during reachability/discovery checks.
  • As a legacy alias, ssrfPolicy.allowPrivateNetwork is still supported.
  • For strict mode, rely on the wildcard-aware ssrfPolicy.allowedHostnames for exact-host and pattern exceptions.
  • Remote profiles are attach-only, meaning start/stop/reset are unavailable.
  • profiles.*.cdpUrl takes http://, https://, ws://, and wss://. Pick HTTP(S) when you want OpenClaw to find /json/version; pick WS(S) when your provider hands you a direct DevTools WebSocket URL.
  • If an externally managed CDP service is reachable via loopback, set that profile's attachOnly: true; otherwise OpenClaw treats the loopback port as a local managed browser profile and may flag local port ownership errors.
  • Profiles using existing-session rely on Chrome MCP rather than CDP and can attach on the chosen host or through a connected browser node.
  • Profiles using extension go through the authenticated OpenClaw Chrome extension relay. Because the relay owns its loopback endpoint, these profiles do not accept cdpUrl. See Chrome extension.
  • For existing-session profiles, userDataDir can be set to point at a specific Chromium-based browser profile such as Brave or Edge.
  • When Chrome is already running behind a DevTools HTTP(S) discovery endpoint or a direct WS(S) endpoint, existing-session profiles can set cdpUrl. In that mode OpenClaw hands the endpoint to Chrome MCP instead of using auto-connect; userDataDir is ignored for Chrome MCP launch arguments.
  • Route limits for current Chrome MCP stay in place for existing-session profiles: snapshot/ref-driven actions instead of CSS-selector targeting, one-file upload hooks, no dialog timeout overrides, no wait --load networkidle, and no responsebody, PDF export, download interception, or batch actions.
  • Local managed openclaw profiles get cdpPort and cdpUrl assigned automatically; set cdpUrl explicitly only for remote CDP profiles or existing-session endpoint attach.
  • Local managed profiles can use executablePath to override the global browser.executablePath for that profile. This is handy for running one profile in Chrome and another in Brave.
  • Auto-detect order: default browser if Chromium-based → Chrome → Brave → Edge → Chromium → Chrome Canary.
  • Both browser.executablePath and browser.profiles.<name>.executablePath accept ~ and ~/... for your OS home directory before Chromium launch. Per-profile userDataDir on existing-session profiles also gets tilde-expanded.
  • Control service: loopback only (port derived from gateway.port, default 18791).
  • Extra launch flags for local Chromium startup are appended by extraArgs (for example --disable-gpu, window sizing, or debug flags).

UI

{
  ui: {
    seamColor: "#FF4500",
    assistant: {
      name: "OpenClaw",
      avatar: "CB", // emoji, short text, image URL, or data URI
    },
    prefs: {
      theme: "claw", // claw | knot | dash | custom
      themeMode: "system", // light | dark | system
      locale: "en",
      chatShowThinking: true,
      chatShowToolCalls: true,
      chatPersistCommentary: true, // Keep commentary after runs in Control UI; does not deliver it to channels
      chatSendShortcut: "enter", // enter | modifier-enter
      chatFollowUpMode: "steer", // steer | queue; omit to use the server queue mode
    },
  },
}
  • seamColor: sets the accent color used by the native app UI chrome, such as the Talk Mode bubble tint.
  • assistant: overrides the Control UI identity. If not set, the active agent identity is used.
  • prefs: stores cross-device operator preferences. This is the canonical location, letting agents modify them via the approval gate while keeping every Control UI client in sync; browsers cache the values in local storage for fast startup. A connection marked read-only keeps edits local to that browser without attempting a config write. Offline changes stay queued until a writable connection is available and persist as browser-local preferences while the connection remains read-only. chatPersistCommentary falls back to true. When set to false, live commentary stays visible during a run but disappears at completion, and new Codex commentary is blocked from entering the durable transcript mirror. Messaging-channel delivery is unaffected. Presentation-only settings, like advanced-tier visibility, text scale, chat width, and live sidebar activity, remain browser-local and are adjusted in Settings. Server-side changes apply live to connected clients: after each persisted config write, the gateway emits a hash-only config.changed event, and clients update their snapshot (this is skipped if a local settings draft has unsaved edits). Reconnecting clients reconcile upon connect.

Desktop

The host desktop source allows the Control UI Desktop panel to reach the Gateway machine. It can connect to an existing loopback RFB server or manage a headless TigerVNC/XFCE desktop on Linux. This is a Labs feature, disabled by default.

{
  desktop: {
    host: {
      enabled: true,
      managed: true,
      // port: 5900, // Setting a port selects attach mode instead.
      // passwordFile: "/path/to/vnc-password.txt",
    },
  },
}
  • desktop.host.enabled: advertises This machine as a desktop source after the Gateway restarts.
  • desktop.host.managed: Linux only. Starts a gateway-supervised, loopback-only TigerVNC/XFCE desktop lazily on first observation and stops it after the desktop session's linger period. Default: false.
  • desktop.host.port: loopback RFB port on 127.0.0.1 (default: 5900).
  • desktop.host.passwordFile: optional UTF-8 VNC password file for attach mode. If absent, the Control UI asks for a VNC password and holds it in browser memory for that connection. Managed mode always generates its own ephemeral password.

OpenClaw connects exclusively via loopback. An explicit port always forces attach mode, and an existing RFB listener on port 5900 overrides managed mode. Managed mode depends on Xtigervnc, tigervncpasswd, and startxfce4; on Debian/Ubuntu, install tigervnc-standalone-server tigervnc-tools xfce4-session. The Gateway creates a fresh temporary VNC password for each managed session, never stores it, and oversees both the VNC server and XFCE session.

Without managed mode, set up third-party servers to listen on loopback where supported. On Linux, use loopback-only TigerVNC or x11vnc; GNOME Remote Desktop's VeNCrypt mode is unsupported. On Windows, turn on VNC authentication and loopback access in the VNC server.

On macOS, enable System Settings → General → Sharing → Screen Sharing. Modern Screen Sharing relies on ARD account authentication, so the Gateway handles that handshake and provides the browser an already-authenticated no-auth RFB stream. The macOS account password never appears in the observe result, URL, or logs. openclaw doctor can suggest an explicitly confirmed sudo launchctl repair when Screen Sharing is off; enabling the macOS system service may expose it on other network interfaces per macOS Sharing settings.

Paired node desktops

A paired macOS, Windows, or Linux node can present its own desktop in the same Control UI Desktop panel. This path is intentionally off by default and always uses an existing node-local RFB server on 127.0.0.1; the Gateway never directs a node to connect to a caller-selected host or port.

On the node machine, enable the desktop source and set up attach mode:

{
  desktop: {
    host: {
      enabled: true,
      port: 5900,
      // passwordFile: "/path/to/vnc-password.txt",
    },
  },
}

Restart the node host after changing this config. managed: true is a Gateway host feature and does not start a managed desktop inside a node host; paired nodes must already have a loopback RFB server.

On the Gateway, explicitly arm the dangerous command and restart:

{
  gateway: {
    nodes: {
      commands: {
        allow: ["desktop.stream"],
        // deny: ["desktop.stream"], // deny always wins
      },
    },
  },
}

The node reconnect advertises desktop.stream as a pairing-surface upgrade. Check openclaw nodes pending, then approve the new request with openclaw nodes approve <requestId>. The node shows up in the Desktop picker only while it is connected and the effective approved command remains permitted.

For VncAuth, desktop.host.passwordFile stays on the node and is delivered only to the Gateway's authenticated relay. Without a password file, the Control UI prompts for the VNC password. macOS ARD credentials are always prompted per observation. The Gateway completes ARD or VNC authentication before exposing a no-auth RFB handshake to the browser, so credentials are not returned in URLs, logs, or RPC results.

Desktop bytes travel over a dedicated outbound binary WebSocket from the node. The normal node invoke remains only as the cancellable lifecycle handle and never carries framebuffer data. Reconnecting or changing the node's pairing generation closes active relays. To disarm the feature, remove desktop.stream from commands.allow or add it to commands.deny, restart the Gateway, and reconnect the node.

If the node is missing from the picker, check all four gates: the node-local desktop config, the loopback RFB listener, the approved pairing update, and the Gateway allow/deny policy. After changing any of them, restart the affected Gateway or node host and review openclaw nodes pending again.


Gateway

{
  gateway: {
    mode: "local", // local | remote
    port: 18789,
    bind: "loopback",
    publicOrigin: "https://gateway.example.com",
    auth: {
      mode: "token", // none | token | password | trusted-proxy
      token: "your-token",
      // password: "your-password", // or OPENCLAW_GATEWAY_PASSWORD
      // trustedProxy: { userHeader: "x-forwarded-user" }, // for mode=trusted-proxy; see /gateway/trusted-proxy-auth
      allowTailscale: true,
      identityScopes: {
        "admin@example.com": ["operator.admin"],
      },
      rateLimit: {
        maxAttempts: 10,
        windowMs: 60000,
        lockoutMs: 300000,
        exemptLoopback: true,
      },
    },
    tailscale: {
      mode: "off", // off | serve | funnel
    },
    controlUi: {
      enabled: true,
      basePath: "/openclaw",
      // environment: { label: "edge", color: "amber" },
      // root: "dist/control-ui",
      // github: { token: { source: "store", provider: "default", id: "CONTROL_UI_GITHUB" } },
      // toolTitles: false, // opt-in AI purpose titles for tool calls (spends utility-model tokens)
      // embedSandbox: "scripts", // strict | scripts | trusted
      // allowExternalEmbedUrls: false, // dangerous: allow absolute external http(s) embed URLs
      // automaticallyFetchFavicons: true, // SSRF-guarded link favicon fetches
      // allowedOrigins: ["https://control.example.com"], // required for non-loopback Control UI
      // dangerouslyAllowHostHeaderOriginFallback: false, // dangerous Host-header origin fallback mode
    },
    cliAgents: {
      enabled: false, // Labs: show create-capable CLI session targets in the model picker
    },
    terminal: {
      enabled: false,
      // shell: "/bin/zsh",
    },
    remote: {
      url: "ws://127.0.0.1:18789",
      transport: "ssh", // ssh | direct
      token: "your-token",
      // password: "your-password",
    },
    trustedProxies: ["10.0.0.1"],
    // Optional. Default false.
    allowRealIpFallback: false,
    nodes: {
      pairing: {
        // Silent same-host pairing and access upgrades. Default: enabled.
        // Set false to require explicit approval for every device.
        autoApproveLocal: true,
        // Optional. Default unset/disabled.
        autoApproveCidrs: ["192.168.1.0/24", "fd00:1234:5678::/64"],
        // SSH-verified auto-approval. Default: enabled (true).
        // Set false to disable SSH verification only; this does not affect
        // autoApproveCidrs above. For manual-only node pairing, set false AND
        // unset autoApproveCidrs. Pass an object to tune: { user, identity,
        // timeoutMs, cidrs }.
        sshVerify: true,
      },
      commands: {
        allow: ["canvas.navigate"],
        deny: ["system.run"],
      },
    },
    tools: {
      // Additional /tools/invoke HTTP denies
      deny: ["browser"],
      // Remove tools from the default HTTP deny list for owner/admin callers
      allow: ["gateway"],
    },
    push: {
      apns: {
        relay: {
          baseUrl: "https://relay.example.com",
          timeoutMs: 10000,
        },
      },
    },
  },
}

Gateway field details

  • mode: local (launch the gateway) or remote (attach to a remote gateway). The gateway will not start unless local is satisfied.

  • port: a single multiplexed port handling both WS and HTTP. Priority order: --port takes precedence over OPENCLAW_GATEWAY_PORT, which outranks gateway.port, and 18789 comes last.

  • publicOrigin: an optional HTTPS origin for the Gateway that is externally reachable, with no path, query string, or credentials included. HTTP is permitted solely for literal loopback hosts (localhost, 127.0.0.1, or [::1]) during local development. Per-requester MCP OAuth depends on this value and employs <publicOrigin>/oauth/mcp/callback as its callback URL. Slack session-card actions and plugin-generated viewer links also rely on this origin. When the Control UI is hosted under a reverse-proxy path prefix, configure gateway.controlUi.basePath separately.

  • bind: options include auto, loopback (the default), lan (0.0.0.0), tailnet (Tailscale IPv4 when available, otherwise loopback), or custom (a single IPv4 address). A resolved tailnet address, as well as any custom address other than 127.0.0.1 or 0.0.0.0, requires 127.0.0.1 on the same port for clients on the same host; startup fails if either listener cannot bind. Exposure beyond loopback stays limited to the chosen interface.

  • Legacy bind aliases: rely on bind mode values in gateway.bind (auto, loopback, lan, tailnet, custom) rather than host aliases (0.0.0.0, 127.0.0.1, localhost, ::, ::1).

  • Docker note: by default, the loopback bind listens on 127.0.0.1 within the container. Under Docker bridge networking (-p 18789:18789), traffic reaches eth0, leaving the gateway unreachable. Opt for --network host, or configure bind: "lan" (or bind: "custom" with customBindHost: "0.0.0.0") to listen across all interfaces.

  • Auth: enabled by default. Any non-loopback bind mandates gateway auth, which in practice means a shared token/password or an identity-aware reverse proxy paired with gateway.auth.mode: "trusted-proxy". The onboarding wizard generates a token by default.

  • When gateway.auth.token and gateway.auth.password are both configured (including SecretRefs), explicitly set gateway.auth.mode to either token or password. If both are present and the mode is left unset, startup and service install/repair flows fail.

  • gateway.auth.mode: "none": a deliberate no-auth mode. Reserve this for trusted local loopback environments; onboarding prompts intentionally do not offer it.

  • gateway.auth.mode: "trusted-proxy": hand off browser/user auth to an identity-aware reverse proxy and accept identity headers from gateway.trustedProxies (refer to Trusted Proxy Auth). This mode expects a non-loopback proxy source by default; same-host loopback reverse proxies need explicit gateway.auth.trustedProxy.allowLoopback = true. Internal same-host callers may use gateway.auth.password as a local direct fallback, while gateway.auth.token stays mutually exclusive with trusted-proxy mode.

  • gateway.auth.allowTailscale: with true active, Tailscale Serve identity headers can fulfill Control UI/WebSocket authentication, validated through tailscale whois. HTTP API endpoints do not rely on that Tailscale header auth; they adhere to the gateway's standard HTTP authentication mode instead. This tokenless mechanism presumes the gateway host is trustworthy. The default is true when tailscale.mode = "serve".

  • gateway.auth.identityScopes: associates a verified trusted-proxy user or Tailscale WhoIs login with connection-only operator scopes. Email keys are matched without case sensitivity; other identities require exact matching. For trusted-proxy Control UI connections, x-openclaw-scopes restricts device enrollment or upgrade requests and the final device-plus-identity session scopes. Grants neither create nor modify pairing records. Token, password, and no-auth connections lack verified identity and thus receive no grant.

  • gateway.auth.rateLimit: an optional limiter for failed authentication attempts. It applies per client IP and per auth scope (shared-secret and device-token are tracked separately). Blocked attempts yield 429 plus Retry-After.

    • On the async Tailscale Serve Control UI path, failed attempts for the same {scope, clientIp} are queued before the failure write. Consequently, concurrent bad attempts from one client can trigger the limiter on the second request rather than both racing through as simple mismatches.
    • gateway.auth.rateLimit.exemptLoopback defaults to true; configure false when you deliberately want localhost traffic rate-limited as well (for test environments or strict proxy setups).
  • Browser-origin WS authentication attempts are always throttled with loopback exemption turned off (defense-in-depth against browser-based localhost brute force).

  • On loopback, those browser-origin lockouts are isolated per normalized Origin value, so repeated failures from one localhost origin do not automatically lock out a different origin.

  • tailscale.mode: serve (tailnet only, loopback bind) or funnel (public, requires auth). OpenClaw holds the route as a foreground claim, so startup fails unless the route is active and the route is released when the Gateway stops. Named Tailscale Services are unsupported because the Tailscale CLI permits them only as persistent background routes.

  • tailscale.preserveFunnel: deprecated migration guard. When true and tailscale.mode = "serve", OpenClaw checks tailscale funnel status before re-applying Serve at startup. If that status cannot be inspected, startup fails before the ordinary Gateway listener opens. An external Funnel that still targets the ordinary Gateway port does not receive managed-ingress provenance. OpenClaw leaves the external route unchanged and warns. The route can use generic proxy attribution only through an explicitly configured gateway.trustedProxies source with a valid forwarded client address; Gateway-protected routes then require configured auth, while aggregate probes and plugin-authenticated webhooks retain their own response and authentication policies. First configure gateway.auth.password (prefer a SecretRef) or OPENCLAW_GATEWAY_PASSWORD, and set gateway.auth.mode to password. Then run openclaw config set gateway.tailscale.mode funnel, followed by openclaw config unset gateway.tailscale.preserveFunnel. Default false.

  • controlUi.allowedOrigins: explicit browser-origin allowlist for Gateway WebSocket connects. Required for public non-loopback browser origins. Private same-origin LAN/Tailnet UI loads from loopback, RFC1918/link-local, .local, .ts.net, or Tailscale CGNAT hosts are accepted without enabling Host-header fallback.

  • controlUi.environment: optional visual identity for distinguishing Gateway environments. Set { label: "edge", color: "amber" } to show a matching top stripe, agent-avatar ring, environment pills, browser-title suffix, and tinted favicon. label is trimmed and must contain 1, 24 characters. color must be teal, amber, purple, coral, pink, blue, green, red, or gray. The label and color are visible before sign-in; omit the setting to keep the default appearance unchanged.

  • controlUi.github.token: optional SecretRef-backed service credential for Control UI GitHub previews and project discovery. Prefer this explicit setting when the Gateway should own GitHub service access independently of its shared process environment. When omitted, the shipped GH_TOKEN then GITHUB_TOKEN process-environment fallback remains active. An explicitly configured but unavailable credential fails closed instead of using that fallback. Its exact environment or store name is excluded from agent execution; a custom name does not clear unrelated native GH_TOKEN or GITHUB_TOKEN values. This credential is separate from tools.github agent identities and does not create an OS-user security boundary.

  • controlUi.toolTitles: opt in to AI-generated purpose titles for tool calls in Control UI chat. Default: false (tool rendering stays fully deterministic with no background model calls). When enabled, the chat.toolTitles method labels complex calls through standard utility-model routing, the agent's utilityModel (an operator decision that may send bounded tool arguments to the chosen provider, like every utility task), or the session provider's declared small-model default (OpenAI → gpt-5.6-luna, Anthropic → claude-haiku-4-5), and caches results in the per-agent state database so repeat views never re-bill. utilityModel: \"\" disables titles like every other utility task; titles never fall back to the primary model.

  • controlUi.automaticallyFetchFavicons: controls favicon linking inside the Control UI chat. Its default is true. Once authenticated, the browser asks the same-origin Gateway to fetch each hostname. The Gateway pulls only https://<hostname>/favicon.ico, blocks IP literals along with private or internal targets, pins public DNS lookups, applies the same strict SSRF checks to every redirect, caps redirects, time, bytes, and concurrency, validates the image, then hands back a blob that can be cached privately. OpenClaw never relies on Google or any other favicon service for this. Because of it, linked hostnames and the Gateway's network address become visible to those destination sites. To stop the browser from hitting favicon routes and the Gateway from reaching link targets, set false.

  • controlUi.dangerouslyAllowHostHeaderOriginFallback: a risky mode that turns on Host-header origin fallback for setups that intentionally depend on Host-header origin policy.

  • cliAgents.enabled: enables the experimental CLI agents group in the Control UI's new-session model picker. Its default is false. The group shows up only when the Gateway advertises sessions.catalog.list, and it lists only catalog providers capable of creating sessions. Picking one launches the same catalog-target new-session flow that the sidebar catalog action uses.

    Catalog providers may also advertise terminal-based session creation. That method works only when Labs cliAgents.enabled is enabled, the Gateway terminal is up, and the chosen provider exposes the capability. Callers pass cwd; if a fresh worktree is required, create one first with worktrees.create, since terminal startup does not provision it.

  • terminal.enabled: the operator terminal scoped to admin users. Its default is true; to disable it, set false. This terminal launches a host PTY inside the chosen agent workspace, picks up the Gateway process environment, and gets denied for agents flagged with sandbox.mode: "all". Turn it off on setups where admin operators should not receive a host shell; toggling it triggers a Gateway restart and refreshes the Control UI content security policy.

  • terminal.shell: the optional shell binary. When left empty, OpenClaw falls back to $SHELL on Unix and %ComSpec% on Windows.

  • terminal.detachedSessionTimeoutSeconds: the duration a terminal session remains alive after its connection drops (such as a page reload or laptop sleep), allowing reattachment via terminal.attach with its recent output replayed. Its default is 300. Set 0 to terminate sessions immediately upon connection loss. Detached sessions continue executing their commands, so reduce this value on shared or publicly reachable hosts.

  • remote.transport: either ssh (the default) or direct (ws/wss). For direct, remote.url must be wss:// on public hosts; plaintext ws:// is permitted only for loopback, LAN, link-local, .local, .ts.net, and Tailscale CGNAT hosts.

  • remote.remotePort: the gateway port on the remote SSH host. It defaults to 18789; use this when the local tunnel port differs from the remote gateway port.

  • remote.tlsFingerprint: the expected SHA-256 certificate fingerprint for a remote wss:// Gateway. The macOS app applies it to both operator/control and companion-node connections. Without an explicit value, macOS records a first-use pin only after normal system trust succeeds.

  • remote.sshHostKeyPolicy: the macOS SSH tunnel host-key policy. strict is the default and demands an already trusted key. openssh is an explicit opt-in to the effective OpenSSH configuration for managed aliases; review matching user and system SSH settings before enabling it. The macOS app and configure-remote reset this policy to strict when changing targets unless explicitly opted in again.

  • gateway.remote.token / .password are remote-client credential fields. They do not configure gateway auth by themselves.

  • gateway.push.apns.relay.baseUrl: the base HTTPS URL for the external APNs relay used after relay-backed iOS builds publish registrations to the gateway. Public App Store builds use the hosted OpenClaw relay. Custom relay URLs must match a deliberately separate iOS build/deployment path whose relay URL points at that relay.

  • gateway.push.apns.relay.timeoutMs: the gateway-to-relay send timeout in milliseconds. Its default is 10000.

  • Relay-backed registrations are delegated to a specific gateway identity. The paired iOS app fetches gateway.identity.get, includes that identity in the relay registration, and forwards a registration-scoped send grant to the gateway. Another gateway cannot reuse that stored registration.

  • OPENCLAW_APNS_RELAY_BASE_URL / OPENCLAW_APNS_RELAY_TIMEOUT_MS: temporary env overrides for the relay config above.

  • OPENCLAW_APNS_RELAY_ALLOW_HTTP=true: development-only escape hatch for loopback HTTP relay URLs. Production relay URLs should stay on HTTPS.

  • OPENCLAW_HANDSHAKE_TIMEOUT_MS: optional environment override for the built-in pre-auth Gateway WebSocket handshake timeout.

  • channels.<provider>.healthMonitor.enabled: per-channel opt-out for health-monitor restarts while keeping the global monitor enabled.

  • channels.<provider>.accounts.<accountId>.healthMonitor.enabled: per-account override for multi-account channels. When set, it takes precedence over the channel-level override.

  • Local gateway call paths can use gateway.remote.* as fallback only when gateway.auth.* is unset.

  • If gateway.auth.token / gateway.auth.password is explicitly configured via SecretRef and unresolved, resolution fails closed (no remote fallback masking).

  • trustedProxies: reverse proxy IPs that terminate TLS or inject forwarded-client headers. Only list proxies you control. Loopback entries are still valid for same-host proxy/local-detection setups (for example Tailscale Serve or a local reverse proxy), but they do not make loopback requests eligible for gateway.auth.mode: "trusted-proxy".

  • allowRealIpFallback: when true, the gateway accepts X-Real-IP if X-Forwarded-For is missing. Default false for fail-closed behavior.

  • gateway.nodes.pairing.autoApproveLocal: silently approves pairing, role upgrades, and scope upgrades from trusted local connections (default: true). Scope upgrades additionally require the connection itself to prove local-grade credentials (auth mode none, or the shared token/password); Tailscale, trusted-proxy, and device-token connects keep their paired scopes as a durable cap. Set false to require explicit approval for every device; metadata-only reconnect refreshes remain automatic.

  • gateway.nodes.pairing.autoApproveCidrs: optional CIDR/IP allowlist for auto-approving first-time node device pairing with no requested scopes. It is disabled when unset. This does not auto-approve operator/browser/Control UI/WebChat pairing, and it does not auto-approve role, scope, metadata, or public-key upgrades.

  • gateway.nodes.pairing.sshVerify: SSH-backed automatic approval for initial node device pairing (default: on). The gateway connects back to the pairing host via SSH (BatchMode, strict host key checking) and grants approval only when the openclaw node identity device key matches exactly. The same eligibility threshold as autoApproveCidrs applies; probes are restricted to private/CGNAT source addresses unless overridden by cidrs. To turn this off, set false, or adjust with { user, identity, timeoutMs, cidrs }. Refer to Node pairing.

  • gateway.nodes.commands.allow / gateway.nodes.commands.deny: global rules for allowing or denying declared node commands after pairing and platform allowlist checks. commands.allow serves as the one-time persistent enable for classified commands, including camera.snap, camera.clip, desktop.stream, screen.record, health.summary, sms.search, and sms.send; commands.deny excludes a command even if a platform default or explicit allow would normally include it. Computer and mobile UI control instead depends on node-local enablement (default off) plus pairing. iOS Health permission, Android SMS permission, and Gateway command authorization operate independently. When a node updates its declared command list, reject and re-approve the device pairing so the gateway retains the refreshed command snapshot.

  • gateway.tools.deny: additional tool names blocked for HTTP POST /tools/invoke (extends the default deny list).

  • gateway.tools.allow: removes tool names from the default HTTP deny list for owner/admin callers. This does not grant identity-bearing operator.write callers owner/admin status; cron, gateway, and nodes stay inaccessible to non-owner callers even when allowlisted.

OpenAI-compatible endpoints

  • Admin HTTP RPC: disabled by default as the admin-http-rpc plugin. Activate the plugin to register POST /api/v1/admin/rpc. See Admin HTTP RPC.
  • Chat Completions: off by default. Turn on with gateway.http.endpoints.chatCompletions.enabled: true.
  • Responses API: gateway.http.endpoints.responses.enabled.
  • Responses URL-input hardening:
    • gateway.http.endpoints.responses.maxUrlParts
    • gateway.http.endpoints.responses.files.urlAllowlist
    • gateway.http.endpoints.responses.images.urlAllowlist Empty allowlists count as unset; use gateway.http.endpoints.responses.files.allowUrl=false and/or gateway.http.endpoints.responses.images.allowUrl=false to stop URL fetching.
  • Optional response hardening header:
    • gateway.http.securityHeaders.strictTransportSecurity (set only for HTTPS origins you manage; see Trusted Proxy Auth)

Multi-instance isolation

Launch multiple gateways on a single host with distinct ports and state directories:

OPENCLAW_CONFIG_PATH=~/.openclaw/a.json \
OPENCLAW_STATE_DIR=~/.openclaw-a \
openclaw gateway --port 19001

Shortcut flags: --dev (uses ~/.openclaw-dev plus port 19001), --profile <name> (uses ~/.openclaw-<name>).

Check Multiple Gateways.

gateway.tls

{
  gateway: {
    tls: {
      enabled: false,
      autoGenerate: false,
      certPath: "/etc/openclaw/tls/server.crt",
      keyPath: "/etc/openclaw/tls/server.key",
      caPath: "/etc/openclaw/tls/ca-bundle.crt",
    },
  },
}
  • enabled: turns on TLS termination at the gateway listener (HTTPS/WSS) (default: false).
  • autoGenerate: creates a local self-signed cert/key pair automatically when explicit files are absent; intended for local/dev scenarios only. Generated files are published without overwriting existing paths, and their parent directories are synced when the filesystem supports it; unsupported directory flushing emits a structured degraded-durability warning.
  • certPath: filesystem path to the TLS certificate file.
  • keyPath: filesystem path to the TLS private key file; keep permission-restricted.
  • caPath: optional CA bundle path for client verification or custom trust chains.

gateway.reload

{
  gateway: {
    reload: {
      mode: "hybrid", // off | hybrid
    },
  },
}
  • mode: determines how config edits are applied at runtime.
    • "off": ignore live edits; changes need an explicit restart.
    • "hybrid" (default): apply hot-safe changes in-process, then restart when a change requires it.

The deprecated "restart" and "hot" settings are no longer in use; openclaw doctor --fix now resolves both to "hybrid".

Debounce timing for reloads and deferral of in-flight operations are handled by built-in defaults and can no longer be adjusted. Older configuration files have the retired debounceMs and deferralTimeoutMs keys stripped out by openclaw doctor --fix.


Cloud worker environments

Cloud workers require explicit opt-in. When cloudWorkers is missing, or profiles comes back empty, OpenClaw refuses any new cloud-worker requests and exposes no Cloud destination. For eligible paired-device targets, sessions.dispatch might stay accessible. The configuration schema, along with the read-only environments.list and environments.status methods, continues to be available. Previously created durable records still reconcile and stay visible, and the existing gateway/node projection remains untouched.

Providers using SSH-backed remote-exec must supply a trusted hostKey that is exactly algorithm base64, with no hostname or comment attached. Bootstrap writes that key to a dedicated known_hosts file, employs StrictHostKeyChecking=yes, and aborts before any connection is opened if the provider leaves it out. No trust-on-first-use fallback exists. These providers also route workspace traffic over separate pinned SSH connections, so rsync cannot interfere with control traffic.

For node-backed worker-turn providers, an authenticated node device id is returned instead. The Gateway installs the current worker bundle and moves the workspace through the node transport; OpenClaw SSH endpoint credentials are neither returned nor resolved by these providers.

Crabbox profile

The bundled crabbox provider spins up a temporary machine via the local Crabbox CLI, registers it as an ephemeral node, and hands back a node lease for worker-turn. The inner settings.provider picks the Crabbox backend, which is distinct from the outer OpenClaw provider id.

{
  cloudWorkers: {
    profiles: {
      production: {
        provider: "crabbox",
        settings: {
          provider: "aws",
          class: "standard",
          ttl: "24h",
          idleTimeout: "60m",
          // Optional absolute path. Default: sibling ../crabbox/bin/crabbox, then PATH.
          binary: "/usr/local/bin/crabbox",
        },
      },
    },
  },
}
  • settings.provider (required): Crabbox backend forwarded via --provider; the direct AWS backend is selected by aws.
  • settings.class (required): Crabbox machine class handed to --class.
  • settings.ttl and settings.idleTimeout (required): positive Go duration strings supplied to --ttl and --idle-timeout as provider-side safeguards.
  • settings.binary: optional absolute path to the Crabbox executable. Without it, OpenClaw looks at the sibling Crabbox checkout, then executable entries on PATH, and finally calls crabbox, so a missing CLI surfaces as a visible provider error.

Any unknown settings are rejected. Credentials for Crabbox and backend-specific account configuration stay under Crabbox's control; keep them out of settings. OpenClaw only invokes the local CLI and makes no provider network calls from this plugin. Provisioning passes a single deterministic canonical lease ID through --lease-id, treats --slug purely as display metadata, and always passes --keep=true; the external lifecycle is owned by OpenClaw, which destroys the lease with crabbox stop --id <canonical-id>. When a result is ambiguous, Gateway reconciliation reruns the same fixed-ID operation. Crabbox must return the exactly attested lease or fail closed; OpenClaw never falls back to slug adoption or replacement allocation.

For coordinator-backed AWS, the Gateway host's outbound IPv4 should appear in Crabbox's own aws.sshCIDRs as a /32. Check it with crabbox config show --json and crabbox doctor --provider aws --json before provisioning; this provider-ingress setting does not belong in OpenClaw settings. Refer to Coordinator-backed Crabbox.

Crabbox setup relies on an environment-owned one-use pairing credential and the configured public Gateway URL. The provider returns the exact authenticated node id; the Gateway then installs its current bundle and transfers the workspace through authenticated node routes. OpenClaw does not persist Crabbox SSH endpoint, key, host-key, or fallback-port output.

Note

For AWS admission, providerMetadata.instanceProfileAttached must be false. Install Crabbox 0.41.1 or newer to get the fixed-ID replay and closed inspection contracts.

Static SSH development profile

{
  cloudWorkers: {
    profiles: {
      development: {
        provider: "static-ssh",
        settings: {
          host: "worker.example.test",
          port: 22,
          user: "openclaw",
          hostKey: "ssh-ed25519 <base64-public-host-key>",
          keyRef: {
            source: "env",
            provider: "default",
            id: "OPENCLAW_WORKER_SSH_KEY",
          },
        },
      },
    },
  },
}
  • profiles: named worker profiles whose ids are non-empty and stripped of surrounding whitespace. Each profile points to a provider that a plugin has registered.
  • provider: a worker provider id that cannot be empty. In the examples, the bundled crabbox provider and the QA Lab static-ssh provider are used.
  • install: the SSH-based remote-exec worker installation approach. By default, "bundle" ships a content-hashed bundle of the gateway's installed build and works with released, development, and unreleased versions. "npm" is an optional optimization meant for an unmodified packaged release; it pulls openclaw@<exact gateway version> from the public npm registry and never installs latest. For Node-backed worker-turn providers, the Gateway bundle is delivered through node transport instead.
  • When configured, bundled provider plugins are picked automatically, though explicit disables and plugins.allow remain in effect. If an allowlist is set, include the provider id, for instance crabbox. External provider plugins additionally require installation and explicit enabling.
  • settings: bounded JSON owned by the provider. The chosen plugin defines and validates its keys; for values that carry secrets, use SecretRef objects. The static SSH provider demands host, user, hostKey, and keyRef; port falls back to 22. hostKey has to be a single OpenSSH public host-key line (algorithm base64) sourced from the known host or another trusted channel, with no options prefix.

A supported Node runtime (22.22.3+, 24.15+, or 25.9+) with WAL-reset-safe SQLite must already be present on the worker. The optional "npm" method additionally needs npm and outbound HTTPS connectivity to the public npm registry. Networked toolchain setup falls under provider policy; bootstrap reports an actionable error rather than installing toolchains itself.

Node-backed worker-turn starts the self-contained worker loop and forwards model inference through the Gateway. SSH-backed remote-exec keeps the model loop on the Gateway and directs sandbox operations to the remote host. Both reconcile the session workspace and transcript via the durable placement lifecycle.

Each durable environment record keeps its validated provider settings and resolved install method in a creation-time profile snapshot. Altering or deleting a named profile affects only new creates; existing records continue lifecycle reconciliation with that snapshot, as long as the owning plugin stays available.

Profile changes demand a Gateway restart. Under the default gateway.reload.mode: "hybrid", the config watcher triggers the restart on its own; "off" mode calls for a manual restart.

Warning

The static-ssh provider is a source-tree QA Lab remote-exec harness and is left out of packaged distributions. A worker on its shared host can access unrelated host data, so this provider must not be treated as a production isolation boundary. Its operator has to supply the expected hostKey; OpenClaw will not learn or accept a key from the first connection. Destroying its lease only clears OpenClaw's logical record; it does not stop or clean the host.


Hooks

{
  hooks: {
    enabled: true,
    token: "shared-secret",
    path: "/hooks",
    defaultSessionKey: "hook:ingress",
    allowRequestSessionKey: true,
    allowedSessionKeyPrefixes: ["hook:", "hook:gmail:"],
    allowedAgentIds: ["hooks", "main"],
    presets: ["gmail"],
    transformsDir: "~/.openclaw/hooks/transforms",
    mappings: [
      {
        match: { path: "gmail" },
        action: "agent",
        // Configure this agent under agents.entries with a restricted tool
        // profile and sandbox before routing untrusted content to it.
        agentId: "hooks",
        wakeMode: "now",
        name: "Gmail",
        sessionKey: "hook:gmail:{{messages[0].id}}",
        sessionMode: "persistent",
        messageTemplate: "From: {{messages[0].from}}\nSubject: {{messages[0].subject}}\n{{messages[0].snippet}}",
        deliver: true,
        channel: "last",
        model: "openai/gpt-5.6-sol",
      },
    ],
  },
}

Auth: Authorization: Bearer <token> or x-openclaw-token: <token>. Query-string hook tokens are rejected.

Validation and safety notes:

  • hooks.enabled=true calls for a non-empty hooks.token.
  • hooks.token should stay separate from active Gateway shared-secret auth (gateway.auth.token / OPENCLAW_GATEWAY_TOKEN or gateway.auth.password / OPENCLAW_GATEWAY_PASSWORD); startup logs a non-fatal security warning when reuse is detected.
  • openclaw security audit marks hook/Gateway auth reuse as a critical finding, including Gateway password auth supplied only at audit time (--auth password --password <password>). Run openclaw doctor --fix to rotate a persisted reused hooks.token, then update external hook senders to use the new hook token.
  • hooks.path cannot be /; use a dedicated subpath such as /hooks.
  • If hooks.allowRequestSessionKey=true, restrict hooks.allowedSessionKeyPrefixes (for example ["hook:"]).
  • If a mapping or preset uses a templated sessionKey, set hooks.allowedSessionKeyPrefixes and hooks.allowRequestSessionKey=true. Static mapping keys do not require that opt-in.

Endpoints:

  • POST /hooks/wake{ text, mode?: "now"|"next-heartbeat", agentId?, sessionKey? }
    • sessionKey depends on mode: "now", is allowed solely when hooks.allowRequestSessionKey=true (default: false), and, if set, has to align with hooks.allowedSessionKeyPrefixes.
    • Any agentId provided must point to an agent that has been configured.
  • POST /hooks/agent{ message, name?, agentId?, sessionKey?, sessionMode?, wakeMode?, deliver?, channel?, to?, accountId?, model?, thinking?, timeoutSeconds? }
    • A agentId given must reference an existing configured agent.
    • sessionKey coming from the request payload is only accepted under hooks.allowRequestSessionKey=true (default: false).
    • By default, sessionMode is "isolated". "persistent" makes use of the resolved session, and demands an explicit request sessionKey, hooks.allowRequestSessionKey=true, and a hooks.allowedSessionKeyPrefixes that is not empty.
    • For direct announce delivery, a concrete channel and to are both necessary; providing just one leads to a failure before the run gets scheduled.
    • To pick a configured, active account for direct announce delivery, accountId needs both channel and to; if the selection is invalid, 400 is returned prior to run initiation.
    • For completion-only hooks, leave both delivery fields out, or use deliver: false to disregard any destination data that was supplied.
    • The request holds for up to 15 seconds waiting for runner admission, not for the run to finish. 200 indicates that the agent runner has been entered.
    • Failures before the run produce { ok: false, error, runId }: 400 when delivery coordinates or account selection are invalid, 409 for conflicts in session admission, 502 for other preparation issues, and 503 once the 15-second admission window runs out. Queued work that times out is canceled and will not execute later.
  • POST /hooks/<name> → resolved through hooks.mappings
    • Mapping sessionKey values produced by templates are considered externally provided, and they also require hooks.allowRequestSessionKey=true.
    • Mapped agent actions go through the same admission wait and yield the same 200/400/409/502/503 results.

Mapping details

  • match.path matches the sub-path that follows /hooks (for instance, /hooks/gmail becomes gmail).
  • For generic paths, match.source matches a payload field.
  • Payload data is read by templates such as {{messages[0].subject}}.
  • A hook action can be returned by a JS/TS module that transform points to.
    • The path given in transform.module must be relative and confined to hooks.transformsDir; absolute paths and traversal attempts are refused.
    • Keep hooks.transformsDir within ~/.openclaw/hooks/transforms; workspace skill directories are not accepted. When openclaw doctor flags this path as invalid, relocate the transform module to the hooks transforms directory or drop hooks.transformsDir.
  • A specific agent is targeted by mapping agentId; unknown mapping IDs fall back to the default agent as before. Direct /hooks/wake and /hooks/agent request IDs must reference an agent that has been configured.
  • allowedAgentIds: effective agent routing is limited, including the default-agent route when agentId is absent (* or omitted means allow all, [] means deny all).
  • defaultSessionKey: an optional fixed session key for hook agent runs when no explicit sessionKey is given.
  • allowRequestSessionKey: /hooks/wake and /hooks/agent callers, plus template-driven mappings, are permitted to set sessionKey (default: false).
  • allowedSessionKeyPrefixes: an optional prefix allowlist for explicit sessionKey values (request plus mapping), such as ["hook:"]. It becomes mandatory when any mapping or preset uses a templated sessionKey.
  • sessionMode: mapping session behavior ("isolated" by default, or "persistent"). Persistent mappings must derive a stable key from sessionKey or hooks.defaultSessionKey; template-derived keys keep the request-key and prefix checks.
  • The final reply is sent to a channel by deliver: true; mapped hooks can use channel: "last".
  • Completion-only behavior for the mapped run is maintained by deliver: false.
  • For this hook run, model overrides the LLM (it must be allowed when a model catalog is configured).

Gmail integration

  • The built-in Gmail preset relies on sessionKey: "hook:gmail:{{messages[0].id}}".
  • This per-message key separates conversation context, not tool or workspace access. Without a custom mapping that defines agentId, the preset uses the default agent.
  • For inboxes you do not trust, direct Gmail to a dedicated reader agent and limit that agent with per-agent sandbox and tool policy. When the reader must alert the main agent, limit the handoff with tools.agentToAgent. For the recommended threat model and model tier, see Prompt injection.
  • The setup wizard configures Gmail transport but does not create the reader agent or the required session-key policy. Before running setup for untrusted mail, apply the full restricted Gmail reader configuration.
  • If you retain that per-message routing, set hooks.allowRequestSessionKey: true and limit hooks.allowedSessionKeyPrefixes to the Gmail namespace, for example ["hook:", "hook:gmail:"].
  • When hooks.allowRequestSessionKey: false is needed, override the preset with a static sessionKey rather than the templated default.
{
  hooks: {
    gmail: {
      account: "openclaw@gmail.com",
      topic: "projects/<project-id>/topics/gog-gmail-watch",
      subscription: "gog-gmail-watch-push",
      pushToken: "shared-push-token",
      hookUrl: "http://127.0.0.1:18789/hooks/gmail",
      includeBody: true,
      maxBytes: 20000,
      renewEveryMinutes: 720,
      serve: { bind: "127.0.0.1", port: 8788, path: "/" },
      tailscale: { mode: "funnel", path: "/gmail-pubsub" },
      model: "openai/gpt-5.6-sol",
      thinking: "high",
    },
  },
}
  • On boot, the Gateway auto-starts gog gmail watch serve when configured. Set OPENCLAW_SKIP_GMAIL_WATCHER=1 to turn it off.
  • Do not run a separate gog gmail watch serve alongside the Gateway.

Canvas widget presenter

{
  plugins: {
    entries: {
      canvas: {
        config: {
          host: {
            enabled: true, // set false, or use OPENCLAW_SKIP_CANVAS_HOST=1
          },
        },
      },
    },
  },
}
  • The host.enabled flag acts as the master toggle for Canvas hosting and comes pre-enabled. It controls access to hosted widget documents located at /__openclaw__/canvas/ and A2UI renderer assets found at /__openclaw__/a2ui/.
  • For local-only setups, leave gateway.bind: "loopback" as is (the default).
  • When binding to non-loopback addresses, these endpoints demand Gateway authentication (token, password, or trusted-proxy), matching the behavior of other Gateway HTTP surfaces.
  • Node WebViews generally omit auth headers; once a macOS node pairs and connects, the Gateway publishes a node-specific capability URL via pluginSurfaceUrls.canvas.
  • Capability URLs tie to the active node WS session and have a short lifespan. There is no IP-based fallback mechanism.
  • Restarting the gateway is mandatory for these changes to take effect.

Discovery

mDNS (Bonjour)

{
  discovery: {
    mdns: {
      mode: "minimal", // minimal | full | off
    },
  },
}
  • With minimal (the default), TXT records exclude both cliPath and sshPort.
  • Choosing full adds cliPath and sshPort; however, LAN multicast advertising still depends on the bundled bonjour plugin being active.
  • off turns off LAN multicast advertising without altering plugin activation status.
  • The bundled bonjour plugin launches automatically on macOS hosts, while Linux, Windows, and containerized Gateway deployments require manual opt-in.
  • When the system hostname qualifies as a valid DNS label, it becomes the default hostname; otherwise openclaw is used. You can override this via OPENCLAW_MDNS_HOSTNAME.
  • Setting OPENCLAW_DISABLE_BONJOUR=1 completely disables mDNS advertising, taking precedence over discovery.mdns.mode.

Wide-area (DNS-SD)

{
  discovery: {
    wideArea: { domain: "openclaw.internal" },
  },
}

Configuring discovery.wideArea.domain activates wide-area discovery and creates a unicast DNS-SD zone at ~/.openclaw/dns/. To discover across networks, combine this with a DNS server (CoreDNS is recommended) and Tailscale split DNS.

Steps: openclaw dns setup --apply.


Environment

env (inline env vars)

{
  env: {
    vars: {
      OPENROUTER_API_KEY: "sk-or-...",
      GROQ_API_KEY: "gsk-...",
    },
    shellEnv: {
      enabled: true,
      timeoutMs: 15000,
    },
  },
}
  • Inline environment variables take effect only when the key is absent from the process environment.
  • .env files: the current working directory .env plus ~/.openclaw/.env (neither overrides pre-existing variables).
  • shellEnv pulls in missing expected keys from your login shell profile.
  • Full precedence details are covered in Environment.

Env var substitution

Any config string can reference environment variables using ${VAR_NAME}:

{
  gateway: {
    auth: { token: "${OPENCLAW_GATEWAY_TOKEN}" },
  },
}
  • Matching is restricted to uppercase names: [A-Z_][A-Z0-9_]*.
  • Missing or empty variables stay visibly unresolved, trigger a warning, and remain inaccessible to consumers that need the value.
  • To get a literal ${VAR} value, escape with $${VAR}.
  • Compatible with $include.

Secrets

Secret references stack on top of plaintext values; the latter continue to function.

secrets.egressProxy

Gateway-owned substitution for shared-store secret entries used by agent exec subprocesses is off by default:

{
  secrets: {
    egressProxy: {
      enabled: false,
      bypassHosts: ["pinned-api.example.com"],
    },
  },
}
  • enabled launches the loopback proxy and ephemeral CA when the Gateway starts. Default: false. A Gateway restart is needed to alter this.
  • bypassHosts is an optional exact-hostname list for authenticated blind CONNECT tunnels aimed at certificate-pinned clients. Sentinels remain unsubstituted on bypassed hosts and fail vendor authentication without exposing plaintext.

For subprocess environment wiring, authentication, fail-closed behavior, and limitations, refer to Secret egress proxy.

SecretRef

Adopt a single object shape:

{ source: "env" | "file" | "exec" | "store", provider: "default", id: "..." }

Validation:

  • provider pattern: ^[a-z][a-z0-9_-]{0,63}$
  • source: "env" id pattern: ^[A-Z][A-Z0-9_]{0,127}$
  • source: "file" id: absolute JSON pointer (for example "/providers/openai/apiKey")
  • source: "exec" id pattern: ^[A-Za-z0-9][A-Za-z0-9._:/#-]{0,255}$ (supports AWS-style secret#json_key selectors)
  • source: "exec" ids must not contain . or .. slash-delimited path segments (for example a/../b is rejected)

Supported credential surface

  • Canonical matrix: SecretRef Credential Surface
  • secrets apply targets supported openclaw.json credential paths.
  • auth-profiles.json refs are included in runtime resolution and audit coverage.

Secret providers config

{
  secrets: {
    providers: {
      default: { source: "env" }, // optional explicit env provider
      filemain: {
        source: "file",
        path: "~/.openclaw/secrets.json",
        mode: "json",
        timeoutMs: 5000,
      },
      vault: {
        source: "exec",
        command: "/usr/local/bin/openclaw-vault-resolver",
        passEnv: ["PATH", "VAULT_ADDR"],
      },
    },
    defaults: {
      env: "default",
      file: "filemain",
      exec: "vault",
    },
  },
}

Notes:

  • file provider supports mode: "json" and mode: "singleValue" (id must be "value" in singleValue mode).
  • File and exec provider paths fail closed when Windows ACL verification is unavailable. Use paths whose ACLs OpenClaw can verify; there is no provider-level bypass.
  • exec provider requires an absolute command path and uses protocol payloads on stdin/stdout.
  • Symlink command paths are rejected. Configure the resolved absolute binary path instead.
  • If trustedDirs is configured, the command path must be inside an approved directory.
  • exec child environment is minimal by default; pass required variables explicitly with passEnv.
  • Secret refs are resolved at activation time into an in-memory snapshot, then request paths read the snapshot only.
  • Active-surface filtering applies during activation: unresolved refs on enabled surfaces fail startup/reload, while inactive surfaces are skipped with diagnostics.

Auth storage

{
  auth: {
    profiles: {
      "anthropic:default": { provider: "anthropic", mode: "api_key" },
      "anthropic:work": { provider: "anthropic", mode: "api_key" },
      "openai:personal": { provider: "openai", mode: "oauth" },
    },
    order: {
      anthropic: ["anthropic:default", "anthropic:work"],
      openai: ["openai:personal"],
    },
  },
}
  • Per-agent profiles are stored at <agentDir>/auth-profiles.json.
  • auth-profiles.json supports value-level refs (keyRef for api_key, tokenRef for token) for static credential modes.
  • Legacy flat auth-profiles.json maps such as { "provider": { "apiKey": "..." } } are not a runtime format; openclaw doctor --fix rewrites them to canonical provider:default API-key profiles with a .legacy-flat.*.bak backup.
  • OAuth-mode profiles (auth.profiles.<id>.mode = "oauth") do not support SecretRef-backed auth-profile credentials.
  • Static runtime credentials come from in-memory resolved snapshots; legacy static auth.json entries are scrubbed when discovered.
  • Legacy OAuth imports from ~/.openclaw/credentials/oauth.json.
  • See OAuth.
  • Secrets runtime behavior and audit/configure/apply tooling: Secrets Management.

Audit

{
  logging: {
    audit: {
      enabled: true,
      executionIdentity: false,
      messages: "off", // off | direct | all
    },
  },
}

The Gateway logs metadata-only audit events for agent runs and tool actions into the shared state database. Message lifecycle metadata is a separate opt-in. The ledger stores identity, timing, tool names, and normalized outcomes, but never prompts, message bodies, tool arguments, results, or raw error text. Message rows do not store raw platform account, conversation, message, and target ids. Run/tool session keys remain available for correlation and can themselves contain platform account or peer ids. Records expire after 30 days and the ledger is capped at 100,000 rows. Query them with openclaw audit or the audit.activity.list Gateway RPC. See Audit history for the full data model, privacy semantics, and coverage limits.

  • enabled: enables the writing of new audit events, with true as the default. The ledger starts enabled, since an audit trail switched on only after something goes wrong can't account for what happened. Setting false halts new event inserts once the Gateway restarts; already stored records remain readable until they expire. Re-enabling it picks up recording from that point onward, leaving the gap unfilled.
  • executionIdentity: keeps a limited attribution context for precise execution tracing, defaulting to false. This privacy-sensitive metadata ships disabled on both fresh installs and upgrades. Gathering it calls for enabled: true; apply openclaw config set logging.audit.executionIdentity true and then reboot the Gateway. No environment-variable alias exists for it.
  • messages: controls the scope of message metadata, with "off" as the default. "direct" logs only known direct conversations. "all" additionally captures group, channel, and unknown conversation types. Either mode stays content-free and swaps raw identifiers for installation-local keyed pseudonyms wherever correlation is possible. These serve as correlation aids, not anonymization; the derivation key lives in the state database, yet RPC and CLI exports omit it.

A root-level audit block has been deprecated; the canonical location is now logging.audit. Because the root config object is strict, an outdated top-level audit block gets rejected. Run openclaw doctor --fix to relocate it to logging.audit.

At startup, the running Gateway captures logging.audit.enabled, logging.audit.executionIdentity, and logging.audit.messages; after altering any of those settings, restart it. Message coverage currently spans accepted inbound messages that reach core dispatch, plus one terminal row per original logical outbound reply payload that reaches shared durable delivery. Plugin-local and direct-send routes that skip those shared boundaries remain uncovered. The bounded background writer operates best-effort, not as a lossless compliance archive.


Logging

{
  logging: {
    level: "info",
    file: "/tmp/openclaw/openclaw.log",
    consoleLevel: "info",
    consoleStyle: "pretty", // pretty | json
    redactPatterns: ["\\bTOKEN\\b\\s*[=:]\\s*([\"']?)([^\\s\"']+)\\1"],
  },
}
  • Default log file: /tmp/openclaw/openclaw-YYYY-MM-DD.log; named profiles fall back to /tmp/openclaw/openclaw-<profile>-YYYY-MM-DD.log.
  • Use logging.file to get a stable path.
  • consoleLevel raises the level to debug when --verbose.
  • consoleStyle: choose "pretty" or "json". The older "compact" value is deprecated; openclaw doctor --fix converts it to "pretty".
  • maxFileBytes: the maximum active log file size in bytes before rotation kicks in, as a positive integer, defaulting to 104857600 (100 MB). OpenClaw retains up to five numbered archives next to the active file.
  • redactPatterns: regexes used for best-effort masking across console output, file logs, OTLP log records, and persisted session transcript text. Enabling this replaces the built-in default patterns for log and transcript output, so keep any defaults you still want; dropping them also disables form-body and structured auth-header redaction. Tool payload redaction works separately and always merges your patterns with the defaults.
  • Redaction stays permanently on and can no longer be toggled. openclaw doctor --fix strips the retired switch from older config files; the runtime always applies tools-mode redaction to logs and transcripts. UI, tool, and diagnostic safety surfaces redact secrets independently of this policy.

Diagnostics

{
  diagnostics: {
    enabled: true,
    flags: ["telegram.*"],

    otel: {
      enabled: false,
      endpoint: "https://otel-collector.example.com:4318",
      tracesEndpoint: "https://traces.example.com/v1/traces",
      metricsEndpoint: "https://metrics.example.com/v1/metrics",
      logsEndpoint: "https://logs.example.com/v1/logs",
      protocol: "http/protobuf",
      headers: { "x-tenant-id": "my-org" },
      serviceName: "openclaw-gateway",
      traces: true,
      metrics: true,
      logs: false,
      logsExporter: "otlp",
      sampleRate: 1.0,
      flushIntervalMs: 5000,
      captureContent: false,
    },

    cacheTrace: {
      enabled: false,
    },
  },
}
  • enabled: primary switch controlling whether instrumentation output is produced at all (defaults to true).
  • flags: a set of flag strings that turn on targeted logging, with wildcard support such as "telegram.*" or "*".
  • otel.enabled: turns on the OpenTelemetry export pipeline, defaulting to false. For the complete setup, signal list, and privacy approach, refer to OpenTelemetry export.
  • otel.endpoint: the collector address used for OTel export.
  • otel.tracesEndpoint / otel.metricsEndpoint / otel.logsEndpoint: optional per-signal OTLP endpoints. If any of these is defined, it takes precedence over otel.endpoint for that particular signal.
  • otel.protocol: "http/protobuf" is the default. Since gRPC export has been deprecated, run openclaw doctor --fix to fix a persisted legacy value or to get manual-edit instructions specific to your source.
  • otel.headers: additional HTTP headers attached to outgoing OTel export requests.
  • otel.serviceName: the service name used in resource attributes.
  • otel.traces / otel.metrics / otel.logs: individually activate trace, metrics, or log export.
  • otel.logsExporter: where log export sends data: "otlp" by default, "stdout" for a single JSON object per stdout line, or "both".
  • otel.sampleRate: sampling rate for traces, ranging from 0 to 1.
  • otel.flushIntervalMs: how often telemetry is flushed, measured in milliseconds.
  • otel.captureContent: opt-in capture of content for OTEL span attributes, off by default. With true, non-system visible message, tool, and tool-definition content plus OTLP log bodies are captured, while provider-internal thinking payloads stay excluded.
  • OTEL_SEMCONV_STABILITY_OPT_IN=gen_ai_latest_experimental: environment toggle that opts into the latest experimental GenAI inference span shape, which uses {gen_ai.operation.name} {gen_ai.request.model} span names, CLIENT span kind, and gen_ai.provider.name in place of the older gen_ai.system. Unless enabled, spans keep openclaw.model.call and gen_ai.system for backward compatibility, and GenAI metrics rely on bounded semantic attributes.
  • OPENCLAW_OTEL_PRELOADED=1: environment toggle for hosts that have already registered a global OpenTelemetry SDK. In that case, OpenClaw skips its own SDK startup and shutdown but keeps diagnostic listeners running.
  • OTEL_EXPORTER_OTLP_TRACES_ENDPOINT, OTEL_EXPORTER_OTLP_METRICS_ENDPOINT, and OTEL_EXPORTER_OTLP_LOGS_ENDPOINT: per-signal endpoint environment variables, applied when the matching config key is absent.
  • OTEL_EXPORTER_OTLP_TRACES_PROTOCOL, OTEL_EXPORTER_OTLP_METRICS_PROTOCOL, and OTEL_EXPORTER_OTLP_LOGS_PROTOCOL: per-signal protocol fallbacks, used when otel.protocol is not set. Each one overrides OTEL_EXPORTER_OTLP_PROTOCOL for its own signal.
  • OTEL_EXPORTER_OTLP_PROTOCOL: shared protocol fallback, applied when neither otel.protocol nor the relevant signal-specific variable is set. Only http/protobuf is accepted. Protocol validation happens separately for each signal, so an unsupported resolved value disables that signal's OTLP exporter without affecting supported sibling signals. Doctor does not modify environment variables.
  • cacheTrace.enabled: log cache trace snapshots for embedded runs, defaulting to false.

Telemetry

{
  telemetry: {
    enabled: false,
    consentedAt: "2026-08-02T12:00:00.000Z",
  },
}
  • enabled: fold anonymous channel names, provider families, plugin count, and recent session count into the routine daily update-check request (default: false). During interactive setup, an explicit opt-in is offered with No thanks preselected; non-interactive setup never turns it on. Setting DO_NOT_TRACK=1 or DO_NOT_TRACK=true permanently disables feature statistics without affecting the update check.
  • consentedAt: ISO timestamp marking when the operator accepted or declined feature statistics. Stops interactive setup from prompting a second time.
  • openclaw telemetry show shows the exact current request; openclaw telemetry on and openclaw telemetry off refresh the preference and consent timestamp.
  • OPENCLAW_TELEMETRY_ENDPOINT: optional full endpoint URL for testing or a self-hosted service. Falls back to https://telemetry.openclaw.ai/api/latest-version.

For the full payload, privacy assurances, and every opt-out option, see Usage telemetry and update checks.


Update

{
  update: {
    channel: "stable", // stable | extended-stable | beta | dev
    checkOnStart: true,

    auto: {
      enabled: false,
    },
  },
}
  • channel: release channel: "stable", "extended-stable", "beta", or "dev". Extended-stable is package-only: foreground commands manage installation, while the Gateway may only emit read-only update hints.
  • checkOnStart: perform update checks via https://telemetry.openclaw.ai/api/latest-version at Gateway startup and no more than once every 24 hours afterward (default: true). The default request carries only the OpenClaw version and platform info in its User-Agent; anonymous feature statistics appear only when telemetry.enabled is true. Choosing false, or selecting OPENCLAW_NO_AUTO_UPDATE=1, blocks all automatic update requests, feature statistics, and update notices, even with auto.enabled set to true. Stored extended-stable selections follow the same read-only hint and 24-hour hint schedule.
  • auto.enabled: activate background auto-update campaigns for stable and beta package installs and dev git installs when checkOnStart is also enabled (default: false). Extended-stable never auto-applies.

ACP

{
  acp: {
    enabled: true,
    dispatch: { enabled: true },
    backend: "acpx",
    fallbacks: ["acpx-secondary"],
    defaultAgent: "main",
    allowedAgents: ["main", "ops"],
    stream: {
      repeatSuppression: true,
      deliveryMode: "live", // live | final_only
    },
  },
}
  • enabled: global ACP feature gate (default: true; use false to hide ACP dispatch and spawn affordances).
  • dispatch.enabled: separate gate for ACP session turn dispatch (default: true). Set false to leave ACP commands visible while preventing execution.
  • backend: default ACP runtime backend id (must correspond to a registered ACP runtime plugin). Install the backend plugin first, and if plugins.allow is configured, include the backend plugin id (for instance acpx) or the ACP backend will fail to load.
  • fallbacks: ordered list of fallback ACP backend ids tried when the primary backend fails early with a transient-looking error (unavailable, rate-limited, quota exhausted, or overloaded) before it has produced any output. Each entry must correspond to a registered ACP runtime plugin backend.
  • defaultAgent: fallback ACP target agent id when spawns do not specify an explicit target.
  • allowedAgents: allowlist of agent ids permitted for ACP runtime sessions; empty means no extra restriction.
  • stream.repeatSuppression: suppress repeated status/tool lines per turn (default: true).
  • stream.deliveryMode: "live" streams incrementally; "final_only" buffers until turn terminal events.
  • stream.tagVisibility: record of tag names to boolean visibility overrides for streamed events.
  • runtime.installCommand: optional install command to run when bootstrapping an ACP runtime environment.

Wizard

Behavior and metadata for CLI guided setup flows (onboard, configure, doctor):

{
  wizard: {
    accessMode: "full",
    appRecommendations: true,
    lastRunAt: "2026-01-01T00:00:00.000Z",
    lastRunVersion: "2026.1.4",
    lastRunCommit: "abc1234",
    lastRunCommand: "configure",
    lastRunMode: "local",
    securityAcknowledgedAt: "2026-01-01T00:00:00.000Z",
  },
}
  • wizard.accessMode: discovery consent chosen at the start of guided onboarding. "full" (recommended) lets setup search for AI apps, keys, and local runtimes automatically; "guarded" makes setup ask once before searching and offers manual configuration instead.

  • wizard.appRecommendations is set to true by default. Switching it to false turns off installed-application suggestions during guided or classic onboarding and prevents Gateway device.apps access. Node hosts still need their own installed-app sharing flag, which is off by default, before they can advertise the command.


Identity

Check the agents.entries identity fields under Agent defaults.


Bridge (legacy, removed)

The TCP bridge is absent from current builds. Nodes communicate through the Gateway WebSocket. bridge.* keys are removed from the config schema (validation errors occur until they are removed; openclaw doctor --fix can eliminate unknown keys).

Legacy bridge config (historical reference)

{
  "bridge": {
    "enabled": true,
    "port": 18790,
    "bind": "tailnet",
    "tls": {
      "enabled": true,
      "autoGenerate": true
    }
  }
}

Automations (cron)

{
  cron: {
    enabled: true,
    triggers: {
      enabled: true,
    },
    webhookToken: "replace-with-dedicated-token", // optional bearer token for outbound webhook auth
    webhookSsrfPolicy: {
      allowedHostnames: ["127.0.0.1"], // optional exact exception for a trusted receiver
    },
    sessionRetention: "24h", // duration string ("0h" disables) or false
  },
}
  • enabled: run stored automation jobs (default: true). Set false to halt all automation execution while keeping jobs intact.
  • triggers.enabled: trigger event-driven automation (default: true). Set false to turn off condition triggers, script payloads, and stream schedules.
  • sessionRetention: duration for retaining completed isolated automation run sessions before SQLite session rows are pruned. Also governs cleanup of archived deleted automation transcripts. Default: 24h; use false or a zero duration like "0h" to disable (negative durations are not allowed).
  • Terminal run history persists for 7 days (lost rows for 24 hours), with a cap of the newest 2000 rows per job and history class as an additional limit.
  • webhookToken: bearer token for automation webhook POST delivery (delivery.mode = "webhook"), if absent no auth header is transmitted.
  • webhookSsrfPolicy: shared outbound SSRF policy for primary, completion, failure-destination, and failure-alert webhooks. Private/internal targets are blocked when omitted. Prefer exact allowedHostnames; use dangerouslyAllowPrivateNetwork: true only for trusted private-network receivers. The narrow fake-IP proxy flags are allowRfc2544BenchmarkRange and allowIpv6UniqueLocalRange.

The cron block is strict; cron.enabled, cron.triggers, cron.webhookToken, cron.webhookSsrfPolicy, cron.sessionRetention, and cron.failureAlert are the only valid keys. The retired cron.webhook fallback URL is removed: runtime delivery relies on per-job delivery.mode = "webhook" plus delivery.to, or delivery.completionDestination when preserving announce delivery. openclaw doctor --fix removes a leftover cron.webhook from existing config files.

cron.failureAlert

{
  cron: {
    failureAlert: {
      enabled: false,
      after: 2,
      cooldownMs: 3600000,
      includeSkipped: false,
      mode: "announce",
      channel: "last",
      to: "channel:C1234567890",
      accountId: "main",
    },
  },
}

cron.failureAlert manages the global alert policy and its default destination. Jobs with an existing failure route are covered by default after 2 consecutive execution failures with a 1-hour cooldown; a cron.failureAlert object explicitly activates/tunes the policy even when no route existed. The retired cron.failureDestination block is merged into it by openclaw doctor --fix.

  • enabled: turns the global policy on or off explicitly. With false, inherited notifications are switched off unless a job defines its own failureAlert object; true switches them on globally. Leaving it out lets route-backed defaults apply.
  • after: how many consecutive failures must occur before an alert is triggered (positive integer, minimum: 1; default: 2).
  • cooldownMs: the smallest gap in milliseconds between repeat alerts for the same job (non-negative integer; default: 3600000).
  • includeSkipped: whether consecutive skipped runs count toward the alert threshold (default: false). Skipped runs are tallied separately and never affect backoff tied to execution errors.
  • mode: how alerts are delivered, either "announce" for a channel message or "webhook" to post at the target inside to. When enough target data is present, "announce" is the fallback.
  • channel: overrides the channel used for announce delivery. "last" falls back to the most recent delivery channel on record.
  • to: the explicit announce target or webhook URL. Webhook mode cannot work without it.
  • accountId: an optional account or channel id that scopes where alerts go.
  • Precedence for routes works like this: per-job failureAlert route fields come first, then per-job delivery.failureDestination stacked on top of these global destination fields, and finally the primary announce target.
  • For a given job, failureAlert: false turns off execution and required-delivery failure alerts, though the auto-disable safety notice still fires. Any per-job failureAlert object turns that job on explicitly and lets you fine-tune it.
  • delivery.bestEffort: true blocks inherited or default execution alerts; an explicit per-job failureAlert still takes precedence.
  • A required completion-delivery failure (status: "ok", completionStatus: "failed") does not add to execution backoff and can only notify right away through a resolved alternate failure destination, never through the failed primary route.
  • delivery.failureDestination works only for sessionTarget="isolated" jobs unless the job's primary delivery.mode happens to be "webhook".

Check Automations. Isolated automation runs get tracked as background tasks.

Media model template variables

Placeholders from templates expand in tools.media.models[].args:

VariableDescription
{{Body}}The complete message payload as received
{{RawBody}}Unwrapped payload, no history or sender metadata
{{BodyStripped}}Payload with group mentions removed
{{From}}Who sent the message
{{To}}Where the message is directed
{{MessageSid}}Identifier of the message on the channel
{{SessionId}}UUID for the active session
{{IsNewSession}}"true" appears when a fresh session starts
{{AttachmentUrl}}Current attachment URL or provider reference
{{AttachmentPath}}Local filesystem path for the current attachment
{{AttachmentContentType}}MIME type of the current attachment
{{AttachmentDir}}Folder that holds AttachmentPath
{{AttachmentIndex}}Source fact index, starting from zero
{{Transcript}}Transcribed audio content
{{Prompt}}Media prompt resolved for CLI entries
{{MaxChars}}Max output characters resolved for CLI entries
{{ChatType}}Either "direct" or "group"
{{GroupSubject}}Group title, when available
{{GroupMembers}}Preview of group participants, when available
{{SenderName}}Display name of the sender, when available
{{SenderE164}}Phone number of the sender, when available
{{Provider}}Provider indicator (whatsapp, telegram, discord, etc.)

The older {{MediaPath}}, {{MediaUrl}}, {{MediaType}}, and {{MediaDir}} names are still usable during the plugin SDK compatibility window, but they are deprecated. New configuration should rely on the Attachment* variables.


Config includes ($include)

Break configuration into several files:

// ~/.openclaw/openclaw.json
{
  gateway: { port: 18789 },
  agents: { $include: "./agents.json5" },
  broadcast: {
    $include: ["./clients/mueller.json5", "./clients/schmidt.json5"],
  },
}

How merging works:

  • One file: the containing object gets replaced.
  • Multiple files: deep merge happens in sequence, with later files taking precedence.
  • Keys at the same level: merged after includes, so included values can be overridden.
  • Includes within includes: allowed up to 10 levels deep.
  • Path resolution: relative to the file doing the including, but the result must stay within the top-level config directory (dirname of openclaw.json). Absolute/../ paths are permitted only if they still resolve inside that boundary. To allow extra roots beyond the config directory, set OPENCLAW_INCLUDE_ROOTS (absolute paths).
  • Constraints: paths cannot include null bytes and must be under 4096 characters both before and after resolution; every included file is limited to 2 MB.
  • Writes by OpenClaw that touch only one top-level section backed by a single-file include go through to that included file. For instance, plugins install modifies plugins: { $include: "./plugins.json5" } inside plugins.json5 while leaving openclaw.json untouched.
  • Root includes, include arrays, and includes with sibling overrides are treated as read-only for OpenClaw-owned writes; such writes fail closed rather than flattening the config.
  • Errors: descriptive messages cover missing files, parse failures, circular includes, malformed path formats, and overly long paths.

13,670 words · updated Aug 25, 2026