#protocol mismatch
1 messages · Page 1 of 1 (latest)
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
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.
error is [ws] protocol mismatch conn=6a8509a0-4f17-4ede-91a2-3d6be38bf021 remote=172.29.1.1 client=openclaw-control-ui webchat vcontrol-ui
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:
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
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:
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.
I ran those commands , didnt help
i get Config path not found: gateway.controlUi.root. Run openclaw config validate to inspect config shape.
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:
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.
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
If the first command shows the gateway protocol is newer than the UI asset’s minProtocol/maxProtocol, that’s a release/build mismatch and should go to GitHub issues: https://github.com/openclaw/openclaw/issues
For immediate unblocking, try the older image tag I mentioned, or rebuild from a clean current checkout without Dockerfile edits and add ffmpeg only via the build arg.
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:
docker compose ps
docker compose logs --tail=80 openclaw-gateway
Also: the earlier gateway.controlUi.root “path not found” is fine. That just means no custom override is set.
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:
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
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
gives me OPENCLAW_IMAGE="ghcr.io/openclaw/openclaw:2026.5.16-beta.4-slim" docker compose config | grep -A2 'image:'
image: ghcr.io/openclaw/openclaw:2026.5.16-beta.4-slim
init: true
network_mode: service:openclaw-gateway
image: ghcr.io/openclaw/openclaw:2026.5.16-beta.4-slim
init: true
networks:
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.
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
The JS dump is enough to show the file exists, but we still need the running container version and the protocol values without the whole bundle.
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
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;
}
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.
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.
{"error":{"message":"Unauthorized","type":"unauthorized"}}
and
openclaw-openclaw-gateway-1 ghcr.io/openclaw/openclaw:2026.5.16-beta.4-slim
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.
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:
- Purge/disable caching in the reverse proxy for OpenClaw, especially
/assets/*and/__openclaw/*. - Make sure the proxy forwards WebSocket upgrades and identity headers on WS, not only HTTP.
- Restart/recreate the gateway container.
- Open the proxy URL in a private/incognito window.
Trusted proxy docs: https://docs.openclaw.ai/gateway/trusted-proxy-auth
If you paste which proxy you use (Caddy/nginx/Traefik/Pomerium/Cloudflare/etc.), I can give the exact no-cache + WebSocket header snippet.
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}
# 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:
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
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
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
If that fixes it, purge browser/Caddy/cache. If it doesn’t, we need the filtered minProtocol/maxProtocol output from the container UI bundle to confirm whether the image itself is mismatched.
doing a curl from the cmd line to the proxy wont work either, it wont use oath so i just get a restriced
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:
- 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))'
- 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.
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.
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
`
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.
on my client i see return{minProtocol:4,maxProtocol:4, the same i get from the control ui output
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
If latest also fails, please file this as a release/image bug with:
- OpenClaw image tag
- gateway log line:
protocol mismatch ... vcontrol-ui - Control UI value:
minProtocol:4,maxProtocol:4 - the grep output above
Issue tracker: https://github.com/openclaw/openclaw/issues
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
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.
If it prints protocol 4, then I’ve been chasing the wrong angle and the log needs the full error details around the mismatch.
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
}
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.
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 {}
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:
- image:
ghcr.io/openclaw/openclaw:2026.5.16-beta.4-slim - OpenClaw version:
2026.5.16-beta.4 - gateway constants:
{ n: 5, r: 5, t: 5 } - Control UI bundle:
minProtocol:4,maxProtocol:4 - log:
[ws] protocol mismatch ... client=openclaw-control-ui webchat vcontrol-ui
That’s enough for maintainers to reproduce the bad packaging.
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.
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.