#protocol mismatch

1 messages · Page 1 of 1 (latest)

eternal gull
#

Ran docker compose build to addin ffmpeg and now my webui gives me Protocol mismatch

lunar totemBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> âś… Mark Solution

oak stirrupBOT
# eternal gull Ran docker compose build to addin ffmpeg and now my webui gives me Protocol mism...

That error means the browser/client bundle and gateway are speaking different OpenClaw gateway protocol versions. Since it started right after a Docker rebuild, I’d treat this as a stale/mixed image or stale web UI asset problem first.

First check the version inside the container:

docker compose run --rm openclaw-cli openclaw --version
docker compose logs --tail=120 openclaw-gateway | grep -i "protocol mismatch\|version"

Then rebuild/recreate the gateway so the UI and backend come from the same build:

docker compose down
docker compose build --no-cache openclaw-gateway openclaw-cli
docker compose up -d
oak stirrupBOT
# eternal gull Ran docker compose build to addin ffmpeg and now my webui gives me Protocol mism...

Then hard-refresh the web UI, or try an incognito window, because the browser may still have the old client bundle cached.

For adding ffmpeg in Docker, the supported path is usually the Docker env hook instead of hand-editing the image:

OPENCLAW_DOCKER_APT_PACKAGES="ffmpeg" ./scripts/docker/setup.sh

Docker docs: https://docs.openclaw.ai/install/docker
Protocol docs: https://docs.openclaw.ai/gateway/protocol

If it still happens after the no-cache rebuild, paste the exact openclaw --version output and the protocol mismatch log line; that line should include the client version and expected protocol.

eternal gull
#

error is [ws] protocol mismatch conn=6a8509a0-4f17-4ede-91a2-3d6be38bf021 remote=172.29.1.1 client=openclaw-control-ui webchat vcontrol-ui

oak stirrupBOT
# eternal gull error is [ws] protocol mismatch conn=6a8509a0-4f17-4ede-91a2-3d6be38bf021 remote...

That log confirms it’s the Control UI bundle itself, not auth. The giveaway is:

client=openclaw-control-ui webchat vcontrol-ui

The UI is sending a protocol range the running gateway rejects. That usually means one of these is true: browser has an old cached JS bundle, the container is serving stale dist/control-ui, or gateway.controlUi.root points at an old mounted UI build.

Can you run these from the repo dir and paste the output?

docker compose run --rm openclaw-cli --version
docker compose run --rm openclaw-cli config get gateway.controlUi.root
docker compose ps

For the likely fix, do this exact cycle:

oak stirrupBOT
# eternal gull error is [ws] protocol mismatch conn=6a8509a0-4f17-4ede-91a2-3d6be38bf021 remote...
docker compose down
docker compose build --no-cache openclaw-gateway openclaw-cli
docker compose up -d openclaw-gateway

Then open the UI in an incognito/private window or clear site data for 127.0.0.1:18789.

One thing I’d specifically check: if gateway.controlUi.root prints a custom path, unset it or rebuild that UI path too. The default should be the bundled dist/control-ui from the same image as the gateway.

Docs: https://docs.openclaw.ai/install/docker and https://docs.openclaw.ai/web/control-ui

eternal gull
#

version is OpenClaw 2026.5.17

oak stirrupBOT
# eternal gull version is OpenClaw 2026.5.17

2026.5.17 is recent enough, so I’d narrow this to “the served Control UI is not the one built with that gateway.”

The next useful check is still:

docker compose run --rm openclaw-cli config get gateway.controlUi.root

If that prints anything custom, unset it and restart:

docker compose run --rm openclaw-cli config unset gateway.controlUi.root
docker compose restart openclaw-gateway

Also, if you edited the Dockerfile just to add ffmpeg, prefer the build arg so the normal Dockerfile still builds dist/control-ui and backend together:

oak stirrupBOT
# eternal gull version is OpenClaw 2026.5.17
docker build --no-cache   --build-arg OPENCLAW_DOCKER_APT_PACKAGES="ffmpeg"   -t openclaw:local   -f Dockerfile .

docker compose up -d --force-recreate openclaw-gateway

Then open a private/incognito window to http://127.0.0.1:18789/. If that works, it was browser cache. If it still mismatches, paste the gateway.controlUi.root output and how you changed the Docker build for ffmpeg.

eternal gull
#

I ran those commands , didnt help

#

i get Config path not found: gateway.controlUi.root. Run openclaw config validate to inspect config shape.

oak stirrupBOT
# eternal gull I ran those commands , didnt help

If the rebuild/cache steps did not change it, this is probably not your ffmpeg change anymore. I checked current source and there’s a plausible Control UI protocol regression: the gateway protocol and the UI’s hardcoded minProtocol/maxProtocol can get out of sync. That would produce exactly this log.

Can you run this inside the Docker setup?

docker compose exec openclaw-gateway sh -lc '
node -e "import(\"./dist/gateway/protocol/version.js\").then(m=>console.log(m)).catch(e=>console.error(e.message))"
grep -R "minProtocol" -n dist/control-ui/assets 2>/dev/null | head -5
'

If that shows gateway protocol 5 while the Control UI asset has minProtocol:4 / maxProtocol:4, that’s an upstream build/release bug. Please file it here with those two outputs and your protocol mismatch log: https://github.com/openclaw/openclaw/issues

Temporary workaround: run a previous known-good Docker image tag instead of the freshly built 2026.5.17 image. The package page currently shows a 2026.5.16 beta tag, so you can test with:

oak stirrupBOT
# eternal gull I ran those commands , didnt help
export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.5.16-beta.4-slim"
docker compose pull
docker compose up -d --force-recreate openclaw-gateway

If that image works, it confirms 2026.5.17’s Control UI bundle is the bad piece.

oak stirrupBOT
# eternal gull i get Config path not found: gateway.controlUi.root. Run openclaw config validat...

That’s actually useful: it means you probably do not have a custom Control UI root override. So the mismatch is coming from the bundled/static UI being served, not a config path pointing at an old folder.

Skip the gateway.controlUi.root branch. The next thing I need is the backend/UI protocol check:

docker compose exec openclaw-gateway sh -lc '
node -e "import(\"./dist/gateway/protocol/version.js\").then(m=>console.log(m)).catch(e=>console.error(e.message))"
grep -R "minProtocol" -n dist/control-ui/assets 2>/dev/null | head -5
'

Also worth running:

docker compose run --rm openclaw-cli status --all
oak stirrupBOT
eternal gull
#

running that exec command outputs nothing

oak stirrupBOT
# eternal gull running that exec command outputs nothing

If that printed literally nothing, let’s make the diagnostic noisier and avoid the quoting/TTY weirdness. Run this exact block:

docker compose exec -T openclaw-gateway sh -lc '
set -x
pwd
node -v
ls -la dist | head
find dist -path "*protocol*version*.js" -o -path "*control-ui*" | head -40
node --input-type=module -e "import("./dist/gateway/protocol/version.js").then(m=>console.log(JSON.stringify(m))).catch(e=>{console.error(e.stack || e.message); process.exit(1)})"
grep -R "minProtocol" -n dist/control-ui 2>/dev/null | head -10 || true
'

If even pwd / node -v prints nothing, then the command is not actually attaching to the running gateway container. In that case paste:

oak stirrupBOT
eternal gull
#

the log just shows many Protocol mismatchs

oak stirrupBOT
# eternal gull the log just shows many Protocol mismatchs

The repeated log lines are probably just the web tab reconnecting. Close every Control UI tab first so the logs stop flooding.

Since exec gave nothing, inspect the image with run instead:

docker compose run --rm --no-deps --entrypoint sh openclaw-gateway -lc '
set -x
node -v
node dist/index.js --version
grep -R "PROTOCOL_VERSION" -n dist/gateway 2>/dev/null | head -10 || true
grep -R "minProtocol" -n dist/control-ui 2>/dev/null | head -10 || true
ls -la dist/control-ui | head || true
'

And also:

oak stirrupBOT
# eternal gull the log just shows many Protocol mismatchs
docker compose ps
docker image inspect openclaw:local --format '{{.Id}} {{.Created}}'

If those grep lines show the gateway and UI disagree, stop trying local cache fixes; it’s a bad/mixed image build. Fastest unblock is to pull a published image tag and run that instead:

export OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.5.16-beta.4-slim"
docker compose pull
docker compose up -d --force-recreate openclaw-gateway

Then open a private/incognito window. If that works, please file the 2026.5.17 mismatch with the command output here: https://github.com/openclaw/openclaw/issues

eternal gull
#

no change

oak stirrupBOT
# eternal gull no change

“No change” likely means Compose is still running the same local image. Let’s verify the actual image it resolved, because export OPENCLAW_IMAGE=... only affects commands run from that same shell.

Run this exactly:

OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.5.16-beta.4-slim" docker compose config | grep -A2 'image:'
OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.5.16-beta.4-slim" docker compose pull openclaw-gateway
OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.5.16-beta.4-slim" docker compose up -d --force-recreate openclaw-gateway
OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.5.16-beta.4-slim" docker compose ps

Then use a private/incognito browser window.

If it still says protocol mismatch after that, please paste the output of those four commands. At this point the exact output matters more than another rebuild attempt. If Compose is definitely running the older GHCR image and it still mismatches, this probably needs a GitHub issue with your Docker compose setup attached: https://github.com/openclaw/openclaw/issues

eternal gull
oak stirrupBOT
# eternal gull gives me OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.5.16-beta.4-slim" docker...

Good, Compose is resolving the fallback image correctly. Now the important part is whether the running container was actually recreated from it.

Run these next:

OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.5.16-beta.4-slim" docker compose pull openclaw-gateway
OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.5.16-beta.4-slim" docker compose up -d --force-recreate --remove-orphans openclaw-gateway
docker compose ps
docker compose exec -T openclaw-gateway node dist/index.js --version
docker compose exec -T openclaw-gateway sh -lc 'grep -R "minProtocol" -n dist/control-ui 2>/dev/null | head -5'

Then close all old UI tabs and open a private/incognito window. If it still mismatches, paste the output from ps, --version, and the grep line. The current output only proves Compose config is right; it does not prove the container changed.

eternal gull
#

the last command just send about 200 lines of js to the console

oak stirrupBOT
# eternal gull the last command just send about 200 lines of js to the console

Yep, that means the UI is bundled/minified, so grep dumped the whole asset. Don’t paste it.

Use this filtered version instead:

docker compose exec -T openclaw-gateway sh -lc '
node -e "const fs=require("fs"),path=require("path");function walk(d,a=[]){for(const f of fs.readdirSync(d)){const p=path.join(d,f); if(fs.statSync(p).isDirectory()) walk(p,a); else if(p.endsWith(".js")) a.push(p)} return a} for (const f of walk("dist/control-ui")) { const s=fs.readFileSync(f,"utf8"); const m=s.match(/minProtocol.{0,80}maxProtocol.{0,80}/); if (m) console.log(f+": "+m[0]); }"
'

Also please run these two and paste only their short output:

docker compose ps
docker compose exec -T openclaw-gateway node dist/index.js --version
oak stirrupBOT
eternal gull
#

docker compose exec -T openclaw-gateway sh -lc '
node -e "const fs=require("fs"),path=require("path");function walk(d,a=[]){for(const f of fs.readdirSync(d)){const p=path.join(d,f); if(fs.statSync(p).isDirectory()) walk(p,a); else if(p.endsWith(".js")) a.push(p)} return a} for (const f of walk("dist/control-ui")) { const s=fs.readFileSync(f,"utf8"); const m=s.match(/minProtocol.{0,80}maxProtocol.{0,80}/); if (m) console.log(f+": "+m[0]); }"
'
[eval]:1
const fs=require(fs),path=require(path);function walk(d,a=[]){for(const f of fs.readdirSync(d)){const p=path.join(d,f); if(fs.statSync(p).isDirectory()) walk(p,a); else if(p.endsWith(.js)) a.push(p)} return a} for (const f of walk(dist/control-ui)) { const s=fs.readFileSync(f,utf8); const m=s.match(/minProtocol.{0,80}maxProtocol.{0,80}/); if (m) console.log(f+:
^
Expression expected

SyntaxError: Unexpected token '.'
at makeContextifyScript (node:internal/vm:194:14)
at compileScript (node:internal/process/execution:388:10)
at evalTypeScript (node:internal/process/execution:260:22)
at node:internal/main/eval_string:71:3

Node.js v24.14.0

oak stirrupBOT
# eternal gull docker compose exec -T openclaw-gateway sh -lc ' node -e "const fs=require("fs")...

That’s just shell quoting eating the inner quotes. Use a heredoc so the JS is passed literally:

docker compose exec -T openclaw-gateway sh -lc 'node <<'"'"'NODE'"'"'
const fs = require("fs");
const path = require("path");

function walk(dir, out = []) {
  for (const name of fs.readdirSync(dir)) {
    const file = path.join(dir, name);
    if (fs.statSync(file).isDirectory()) walk(file, out);
    else if (file.endsWith(".js")) out.push(file);
  }
  return out;
}
oak stirrupBOT
# eternal gull docker compose exec -T openclaw-gateway sh -lc ' node -e "const fs=require("fs")...
for (const file of walk("dist/control-ui")) {
  const text = fs.readFileSync(file, "utf8");
  const match = text.match(/minProtocol.{0,120}maxProtocol.{0,120}/);
  if (match) console.log(file + ": " + match[0]);
}
NODE'

And separately, this one is the most important short check:

docker compose exec -T openclaw-gateway node dist/index.js --version

The Node v24.14.0 part is only the runtime, not the OpenClaw version.

eternal gull
#

i get OpenClaw 2026.5.16-beta.4

oak stirrupBOT
# eternal gull i get OpenClaw 2026.5.16-beta.4

Good, the running container is now the fallback image. If the UI still shows protocol mismatch after that, the browser may still be hitting cached assets or a different gateway than this container.

Please try these checks:

curl -s http://127.0.0.1:18789/__openclaw/control-ui-config.json
docker compose ps
docker compose logs --tail=20 openclaw-gateway

The curl output should include "serverVersion":"2026.5.16-beta.4". If it does, close all OpenClaw tabs, clear site data for 127.0.0.1:18789, then open a private/incognito window.

If curl does not show 2026.5.16-beta.4, your browser is reaching a different gateway/process than the container you checked. In that case paste the curl and docker compose ps output.

eternal gull
oak stirrupBOT
# eternal gull {"error":{"message":"Unauthorized","type":"unauthorized"}} and openclaw-open...

That Unauthorized is expected for that JSON endpoint without the gateway token, so it doesn’t tell us the server version yet. It does confirm you’re hitting an OpenClaw gateway on that port.

Run it with the token from your Docker .env file, but don’t paste the token itself:

set -a
. ./.env
set +a
curl -s -H "Authorization: Bearer $OPENCLAW_GATEWAY_TOKEN"   http://127.0.0.1:18789/__openclaw/control-ui-config.json

If that returns "serverVersion":"2026.5.16-beta.4", then the fallback container is definitely what your browser reaches. At that point the fix is browser-side: close all OpenClaw tabs, clear site data for 127.0.0.1:18789 / localhost:18789, then open a private window.

If it returns a different version, there’s another gateway/process or proxy in the path. Paste only the JSON response with secrets removed.

eternal gull
#

i dont have a token, i use proxy auth

oak stirrupBOT
# eternal gull i dont have a token, i use proxy auth

Ah, proxy auth changes the test. Direct curl http://127.0.0.1:18789/... bypasses your proxy, so Unauthorized is expected.

For this setup, check through the same proxy URL your browser uses:

curl -i https://YOUR_OPENCLAW_DOMAIN/__openclaw/control-ui-config.json

If your proxy requires browser login/cookies, that curl may also 401, which is fine. The important practical bit: because you are behind a proxy, also clear/purge the proxy cache, not just the browser cache. A cached old dist/control-ui JS bundle in the proxy would keep sending the wrong minProtocol/maxProtocol even though the backend container is now 2026.5.16-beta.4.

So I’d do this order:

  1. Purge/disable caching in the reverse proxy for OpenClaw, especially /assets/* and /__openclaw/*.
  2. Make sure the proxy forwards WebSocket upgrades and identity headers on WS, not only HTTP.
  3. Restart/recreate the gateway container.
  4. Open the proxy URL in a private/incognito window.
oak stirrupBOT
eternal gull
#

i am using caddy

oak stirrupBOT
# eternal gull i am using caddy

For Caddy, WebSockets are usually handled automatically by reverse_proxy, so I’d focus on cached UI assets and making sure the identity header is passed on the upgraded WS request too.

Use something shaped like this, adjusted for your auth plugin/header names:

openclaw.example.com {
    # your auth block here

    @openclaw_static path /assets/* /__openclaw/*
    header @openclaw_static Cache-Control "no-store, no-cache, must-revalidate, max-age=0"

    reverse_proxy openclaw-gateway:18789 {
        header_up X-Forwarded-Proto {scheme}
        header_up X-Forwarded-Host {host}
        header_up X-Forwarded-For {remote_host}
oak stirrupBOT
# eternal gull i am using caddy
        # Must match gateway.auth.trustedProxy.userHeader
        header_up X-Forwarded-User {http.auth.user.email}
    }
}

Then reload Caddy and force a clean browser path:

docker compose restart openclaw-gateway
caddy reload --config /path/to/Caddyfile

Then clear site data for your OpenClaw domain, or use a private window.

Also check your OpenClaw config matches that header exactly:

oak stirrupBOT
# eternal gull i am using caddy
gateway: {
  bind: "lan",
  trustedProxies: ["<caddy container/proxy IP>"],
  auth: {
    mode: "trusted-proxy",
    trustedProxy: {
      userHeader: "x-forwarded-user",
      requiredHeaders: ["x-forwarded-proto", "x-forwarded-host"]
    }
  }
}

If your Caddy is in Docker, trustedProxies needs the Caddy container/network IP as seen by OpenClaw, not your public IP. Docs: https://docs.openclaw.ai/gateway/trusted-proxy-auth

eternal gull
#

the proxy works, i dont need that fixed. you told me to do a curl via 127.0.0.1 with a token, that wont go though the proxy

oak stirrupBOT
# eternal gull the proxy works, i dont need that fixed. you told me to do a curl via 127.0.0.1 ...

You’re right. I gave you a token/127.0.0.1 check before accounting for your trusted-proxy setup. That was the wrong diagnostic for your config.

Ignore the token curl path. Since your browser goes through Caddy, the meaningful checks are:

curl -I https://YOUR_OPENCLAW_DOMAIN/
curl -I https://YOUR_OPENCLAW_DOMAIN/assets/
docker compose exec -T openclaw-gateway node dist/index.js --version

But if the proxy is known-good and you’re still getting only protocol mismatch, the most likely remaining cause is a stale Control UI JS bundle being served/cached somewhere in the browser/proxy path. The backend is now confirmed as 2026.5.16-beta.4; the browser-side client is what is negotiating the wrong protocol.

The fastest clean test is: open the Caddy URL from a completely different browser/device, or temporarily add a cache-busting query like:

https://YOUR_OPENCLAW_DOMAIN/?v=clean-20260517
oak stirrupBOT
eternal gull
#

doing a curl from the cmd line to the proxy wont work either, it wont use oath so i just get a restriced

oak stirrupBOT
# eternal gull doing a curl from the cmd line to the proxy wont work either, it wont use oath s...

Correct, no more curl. With OAuth in front, command-line curl is the wrong tool unless you export browser cookies, and that’s not worth doing here.

Let’s avoid the proxy entirely and compare two things:

  1. What protocol the running gateway expects:
docker compose exec -T openclaw-gateway node --input-type=module -e 'import("./dist/gateway/protocol/version.js").then(m=>console.log(m.PROTOCOL_VERSION))'
  1. What protocol the bundled Control UI JS contains, without dumping the whole file:
docker compose exec -T openclaw-gateway sh -lc "grep -RaoE 'minProtocol.{0,40}maxProtocol.{0,40}' dist/control-ui | head -5"

If those disagree, the container image is bad/mixed.

oak stirrupBOT
# eternal gull doing a curl from the cmd line to the proxy wont work either, it wont use oath s...

If they agree, then the container is internally consistent and the browser is loading some other/stale UI asset. Since curl can’t authenticate, check that in the browser: DevTools → Network → check “Disable cache” → hard reload → open the loaded JS asset → search for minProtocol. That value should match the container output above.

Sorry for steering you through token/proxy checks earlier; trusted-proxy + OAuth makes those misleading.

eternal gull
#

the first command gives me this

`docker compose exec -T openclaw-gateway node --input-type=module -e 'import("./dist/gateway/protocol/version.js").then(m=>console.log(m.PROTOCOL_VERSION))'
node:internal/modules/esm/resolve:275
throw new ERR_MODULE_NOT_FOUND(
^

Error [ERR_MODULE_NOT_FOUND]: Cannot find module '/app/dist/gateway/protocol/version.js' imported from /app/[eval1]
at finalizeResolution (node:internal/modules/esm/resolve:275:11)
at moduleResolve (node:internal/modules/esm/resolve:865:10)
at defaultResolve (node:internal/modules/esm/resolve:991:11)
at #cachedDefaultResolve (node:internal/modules/esm/loader:719:20)
at #resolveAndMaybeBlockOnLoaderThread (node:internal/modules/esm/loader:736:38)
at ModuleLoader.resolveSync (node:internal/modules/esm/loader:765:52)
at #resolve (node:internal/modules/esm/loader:701:17)
at ModuleLoader.getOrCreateModuleJob (node:internal/modules/esm/loader:621:35)
at onImport.tracePromise.proto (node:internal/modules/esm/loader:650:32)
at TracingChannel.tracePromise (node:diagnostics_channel:350:14) {
code: 'ERR_MODULE_NOT_FOUND',
url: 'file:///app/dist/gateway/protocol/version.js'
}

Node.js v24.14.0
`

oak stirrupBOT
# eternal gull the first command gives me this `docker compose exec -T openclaw-gateway node ...

That means the fallback image predates the split to dist/gateway/protocol/version.js. Use the older location:

docker compose exec -T openclaw-gateway sh -lc "grep -RaoE 'PROTOCOL_VERSION[[:space:]]*=[[:space:]]*[0-9]+' dist/gateway dist/index.js 2>/dev/null | head -10"

And for the Control UI side:

docker compose exec -T openclaw-gateway sh -lc "grep -RaoE 'minProtocol.{0,80}maxProtocol.{0,80}' dist/control-ui 2>/dev/null | head -5"

If the first one doesn’t find anything because the backend is bundled too tightly, use the log as the backend evidence: protocol mismatch logs include the server rejecting the UI before auth, so the UI protocol value is the useful one now.

Also, since you’re on the fallback image and still seeing the same behavior, I’d test one very direct browser-side thing: DevTools → Network → disable cache → hard reload → click the loaded JS asset → search minProtocol. If that value differs from the container’s dist/control-ui grep output, Caddy/browser is serving stale assets. If it matches, the image itself is internally mismatched and this needs an upstream issue.

eternal gull
#

on my client i see return{minProtocol:4,maxProtocol:4, the same i get from the control ui output

oak stirrupBOT
# eternal gull on my client i see return{minProtocol:4,maxProtocol:4, the same i get from the c...

That rules out browser/Caddy cache. Your browser is loading the same bundled Control UI as the container, and that UI is hardcoded to minProtocol:4,maxProtocol:4.

So now the problem is inside the image/runtime: the UI bundle says protocol 4, but the running gateway is rejecting it. We need the gateway’s expected protocol, but the conclusion is already pretty strong: this is a mismatched release/image, not your proxy.

Try extracting the gateway side from the bundled server like this:

docker compose exec -T openclaw-gateway sh -lc "grep -RaoE 'expectedProtocol.{0,80}|PROTOCOL_VERSION.{0,80}|protocol mismatch.{0,80}' dist/index.js dist/gateway 2>/dev/null | head -30"

For unblocking, I’d stop chasing local config and switch image tags until UI and gateway match. Since 2026.5.16-beta.4 still has UI protocol 4 and fails, try the current published non-slim/same tag or a newer beta/main image if available:

OPENCLAW_IMAGE='ghcr.io/openclaw/openclaw:latest' docker compose pull openclaw-gateway
OPENCLAW_IMAGE='ghcr.io/openclaw/openclaw:latest' docker compose up -d --force-recreate --remove-orphans openclaw-gateway
oak stirrupBOT
eternal gull
#

docker compose exec -T openclaw-gateway sh -lc "grep -RaoE 'expectedProtocol.{0,80}|PROTOCOL_VERSION.{0,80}|protocol mismatch.{0,80}' dist/index.js dist/gateway 2>/dev/null | head -30"
dist/gateway/protocol/index.js:PROTOCOL_VERSION, r as PROTOCOL_VERSION, t as MIN_CLIENT_PROTOCOL_VERSION } from "../../version-
dist/gateway/protocol/index.js:PROTOCOL_VERSION, MIN_PROBE_PROTOCOL_VERSION, MessageActionParamsSchema, ModelsListParamsSchema,
dist/gateway/protocol/index.js:PROTOCOL_VERSION, PluginsSessionActionParamsSchema, PluginsSessionActionResultSchema, PluginsUiD

oak stirrupBOT
# eternal gull docker compose exec -T openclaw-gateway sh -lc "grep -RaoE 'expectedProtocol.{0,...

That output points to the actual protocol constants being in a bundled dist/version-*.js chunk. Run this to print them directly:

docker compose exec -T openclaw-gateway sh -lc 'node --input-type=module -e '"'"'
for (const f of process.argv.slice(1)) {
  const m = await import("./" + f);
  console.log(f, {
    PROTOCOL_VERSION: m.PROTOCOL_VERSION,
    MIN_CLIENT_PROTOCOL_VERSION: m.MIN_CLIENT_PROTOCOL_VERSION,
    MIN_PROBE_PROTOCOL_VERSION: m.MIN_PROBE_PROTOCOL_VERSION
  });
}
'"'"' dist/version*.js'

If that prints PROTOCOL_VERSION: 5 or MIN_CLIENT_PROTOCOL_VERSION: 5, then it fully explains the failure: the gateway requires protocol 5 while the bundled UI is protocol 4.

oak stirrupBOT
eternal gull
#

docker compose exec -T openclaw-gateway sh -lc 'node --input-type=module -e '"'"'
for (const f of process.argv.slice(1)) {
const m = await import("./" + f);
console.log(f, {
PROTOCOL_VERSION: m.PROTOCOL_VERSION,
MIN_CLIENT_PROTOCOL_VERSION: m.MIN_CLIENT_PROTOCOL_VERSION,
MIN_PROBE_PROTOCOL_VERSION: m.MIN_PROBE_PROTOCOL_VERSION
});
}
'"'"' dist/version*.js'
dist/version-B2G3zXnp.js {
PROTOCOL_VERSION: undefined,
MIN_CLIENT_PROTOCOL_VERSION: undefined,
MIN_PROBE_PROTOCOL_VERSION: undefined
}
dist/version-C1-6jjhw.js {
PROTOCOL_VERSION: undefined,
MIN_CLIENT_PROTOCOL_VERSION: undefined,
MIN_PROBE_PROTOCOL_VERSION: undefined
}
dist/version-CSUXetqM.js {
PROTOCOL_VERSION: undefined,
MIN_CLIENT_PROTOCOL_VERSION: undefined,
MIN_PROBE_PROTOCOL_VERSION: undefined
}
dist/version-CVCkYMPA.js {
PROTOCOL_VERSION: undefined,
MIN_CLIENT_PROTOCOL_VERSION: undefined,
MIN_PROBE_PROTOCOL_VERSION: undefined
}

oak stirrupBOT
# eternal gull docker compose exec -T openclaw-gateway sh -lc 'node --input-type=module -e '"'"...

The chunk exports are minified aliases, so my import looked for the friendly names and got undefined. Try this instead; it prints the actual exported keys/values from those version chunks:

docker compose exec -T openclaw-gateway sh -lc 'node --input-type=module -e '"'"'
for (const f of process.argv.slice(1)) {
  const m = await import("./" + f);
  const nums = Object.fromEntries(Object.entries(m).filter(([, v]) => typeof v === "number"));
  console.log(f, nums);
}
'"'"' dist/version*.js'

Given the UI is definitely minProtocol:4,maxProtocol:4, we’re looking for whether one of those numeric exports is 5. If yes, that’s the mismatch.

Also fair warning: we’re now deep in bundled internals. For practical recovery, I’d try OPENCLAW_IMAGE=ghcr.io/openclaw/openclaw:latest or rebuild from a clean current checkout. If the same mismatch happens there, this is ready for a GitHub issue with the evidence you already collected.

eternal gull
#

docker compose exec -T openclaw-gateway sh -lc 'node --input-type=module -e '"'"'
for (const f of process.argv.slice(1)) {
const m = await import("./" + f);
const nums = Object.fromEntries(Object.entries(m).filter(([, v]) => typeof v === "number"));
console.log(f, nums);
}
'"'"' dist/version*.js'
dist/version-B2G3zXnp.js {}
dist/version-C1-6jjhw.js {}
dist/version-CSUXetqM.js { n: 5, r: 5, t: 5 }
dist/version-CVCkYMPA.js {}

oak stirrupBOT
# eternal gull docker compose exec -T openclaw-gateway sh -lc 'node --input-type=module -e '"'"...

That confirms it.

Your running gateway bundle has protocol constants 5/5/5, while the bundled Control UI is sending minProtocol:4,maxProtocol:4. That is an image/release mismatch, not Caddy, OAuth, browser cache, or your ffmpeg change.

For immediate recovery, use an image/build where both sides match. Try latest or a newer published tag:

OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest" docker compose pull openclaw-gateway
OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:latest" docker compose up -d --force-recreate --remove-orphans openclaw-gateway
docker compose exec -T openclaw-gateway node dist/index.js --version

Then re-check the browser.

Please file this as a GitHub issue: https://github.com/openclaw/openclaw/issues

Include:

oak stirrupBOT
eternal gull
#

what image should i use to fix my issue right now

oak stirrupBOT
# eternal gull what image should i use to fix my issue right now

Use the last pre-protocol-5 image for now:

OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.4.23-slim" docker compose pull openclaw-gateway
OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.4.23-slim" docker compose up -d --force-recreate --remove-orphans openclaw-gateway
docker compose exec -T openclaw-gateway node dist/index.js --version

If Docker says that tag does not exist, try the non-slim tag:

OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.4.23" docker compose pull openclaw-gateway
OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.4.23" docker compose up -d --force-recreate --remove-orphans openclaw-gateway

Reason: your current image has gateway protocol 5 but Control UI protocol 4. 2026.4.23 is before that protocol bump, so it should avoid the mismatch while the 2026.5.x image gets fixed.

eternal gull
#

that image fixed it perfectly

oak stirrupBOT
# eternal gull that image fixed it perfectly

Good, that confirms it was the 2026.5.x Docker image mismatch: gateway protocol 5 bundled with Control UI protocol 4.

Please file that upstream when you have a minute: https://github.com/openclaw/openclaw/issues

Include the evidence from above:

  • broken image: 2026.5.16-beta.4-slim / 2026.5.17
  • gateway constants: { n: 5, r: 5, t: 5 }
  • Control UI: minProtocol:4,maxProtocol:4
  • workaround: ghcr.io/openclaw/openclaw:2026.4.23-slim

And if this fixed the thread, please right-click the solution message → Apps → ✅ Mark Solution so Answer Overflow indexes it for the next person.