#20381: feat(gateway): make chat history byte limit configurable via `gateway.maxChatHistoryBytes`
gateway
size: XS
## Summary
- **Problem:** The WebSocket `maxPayload` limit (25 MB) is hardcoded in the gateway bundle. Users working with large payloads (browser screenshots as base64, file attachments over WS) cannot raise it without patching compiled JS.
- **Why it matters:** A single high-res browser screenshot can exceed 25 MB as base64, disconnecting the client with "Max payload size exceeded" and no recovery path.
- **What changed:** Added `gateway.maxPayloadBytes` config key in `openclaw.json`. The value is read once at startup and applied to both the primary and operator WebSocket servers. Falls back to 25 MB when unset.
- **What did NOT change:** The default behavior is identical — no existing deployments are affected. `MAX_BUFFERED_BYTES` remains hardcoded (it's a send-side guard, not user-facing). The deprecated `MAX_PAYLOAD_BYTES` constant is preserved for any downstream imports.
## Change Type (select all)
- [ ] Bug fix
- [x] Feature
- [ ] Refactor
- [ ] Docs
- [ ] Security hardening
- [ ] Chore/infra
## Scope (select all touched areas)
- [x] Gateway / orchestration
- [ ] Skills / tool execution
- [ ] Auth / tokens
- [ ] Memory / storage
- [ ] Integrations
- [x] API / contracts
- [ ] UI / DX
- [ ] CI/CD / infra
## Linked Issue/PR
- No linked issue — this is a standalone feature request.
## User-visible / Behavior Changes
- New optional config key: `gateway.maxPayloadBytes` (number, bytes). When set in `openclaw.json`, overrides the default 25 MB WebSocket payload limit.
- The configured value is also reported to clients in the handshake `policy.maxPayload` field, so clients can adjust their own limits accordingly.
- Default behavior is unchanged when the key is absent.
## Security Impact (required)
- New permissions/capabilities? `No`
- Secrets/tokens handling changed? `No`
- New/changed network calls? `No`
- Command/tool execution surface changed? `No`
- Data access scope changed? `No`
## Repro + Verification
### Environment
- OS: macOS / Linux (Docker)
- Runtime/container: OpenClaw v2026.2.18
- Model/provider: Any
- Integration/channel: WebSocket (Control UI / WebChat)
- Relevant config: `{ "gateway": { "maxPayloadBytes": 104857600 } }` (100 MB)
### Steps
1. Set `gateway.maxPayloadBytes` to a value above 25 MB in `openclaw.json`.
2. Restart the gateway.
3. Send a WebSocket message exceeding 25 MB (e.g. a base64 browser screenshot via computer use).
### Expected
- Message is accepted and processed. Handshake `policy.maxPayload` reflects the configured value.
### Actual
- Without this change: client is disconnected with "Max payload size exceeded".
## Evidence
- [ ] Failing test/log before + passing after
- [ ] Trace/log snippets
- [ ] Screenshot/recording
- [ ] Perf numbers (if relevant)
Evidence will be provided after testing on the fork build. The change is minimal and follows the existing pattern used by `getMaxChatHistoryMessagesBytes` / `__setMaxChatHistoryMessagesBytesForTest`.
## Human Verification (required)
- **Verified scenarios:** Code review of all `MAX_PAYLOAD_BYTES` references in the codebase to confirm both usage sites (WS server creation in `server-runtime-state.ts`, handshake policy in `message-handler.ts`) are updated.
- **Edge cases checked:** Invalid config values (negative, zero, NaN, non-number) all fall back to the 25 MB default. Omitting the key preserves existing behavior.
- **What I did not verify:** Load testing with extremely large payloads (>1 GB). The `ws` library itself may have its own practical limits.
## Compatibility / Migration
- Backward compatible? `Yes` — the default is unchanged; no config migration needed.
- Config/env changes? `Yes` — new optional key `gateway.maxPayloadBytes`.
- Migration needed? `No`
## Failure Recovery (if this breaks)
- **How to disable/revert:** Remove `gateway.maxPayloadBytes` from `openclaw.json` and restart. The gateway reverts to the hardcoded 25 MB default.
- **Files/config to restore:** `openclaw.json` only.
- **Known bad symptoms:** Setting an extremely large value could allow a malicious or buggy client to exhaust server memory with a single message. This is an operator-configured value on a self-hosted system, so the operator accepts the tradeoff.
## Risks and Mitigations
- **Risk:** An operator sets an unreasonably large value and a single WS message exhausts memory.
- **Mitigation:** This is an opt-in config on a self-hosted, typically single-user system. The default remains 25 MB. Documentation in the JSDoc notes the default and purpose.
<!-- greptile_comment -->
<h3>Greptile Summary</h3>
This PR introduces a configurable `gateway.maxPayloadBytes` config key to override the hardcoded 25 MB WebSocket payload limit. The implementation adds `initMaxPayloadBytes()`/`getMaxPayloadBytes()` helpers in `server-constants.ts`, wires them into WS server creation and the handshake policy, and adds the TypeScript type.
**Critical issue found:**
- **Missing Zod schema entry**: `maxPayloadBytes` was added to the `GatewayConfig` TypeScript type but not to the Zod validation schema in `src/config/zod-schema.ts`. The gateway schema uses `.strict()`, which rejects unknown keys. Setting `gateway.maxPayloadBytes` in `openclaw.json` will fail config validation with `"Unrecognized key"`, making this feature non-functional. Add `maxPayloadBytes: z.number().int().positive().optional()` to the gateway object in the Zod schema.
**Other observations:**
- `MAX_BUFFERED_BYTES` (50 MB) remains hardcoded at 2× the old default. If an operator configures `maxPayloadBytes` above 50 MB, the send buffer limit could be smaller than a single message, potentially causing connection drops on large server→client responses.
- The client-side `maxPayload` in `client.ts:114` stays hardcoded at 25 MB and doesn't read `policy.maxPayload` from the handshake. This is acceptable for the initial use case (large client→server uploads) but worth noting for completeness.
- The commit message references `gateway.maxChatHistoryBytes` while the actual config key is `gateway.maxPayloadBytes`.
<h3>Confidence Score: 2/5</h3>
- This PR has a blocking bug — the new config key will be rejected by Zod validation at startup, making the feature non-functional.
- The core logic (init/get helpers, WS server wiring, handshake policy) is correctly implemented, but the missing Zod schema entry means the feature cannot be used as intended. The gateway schema uses `.strict()`, so `gateway.maxPayloadBytes` in `openclaw.json` will fail config validation. This must be fixed before merging.
- `src/config/zod-schema.ts` (not in the changeset but needs a corresponding entry for `maxPayloadBytes`), `src/gateway/server-constants.ts` (`MAX_BUFFERED_BYTES` may need to scale with the configured payload limit)
<sub>Last reviewed commit: fbe9351</sub>
<!-- greptile_other_comments_section -->
<sub>(2/5) Greptile learns from your feedback when you react with thumbs up/down!</sub>
<!-- /greptile_comment -->
Most Similar PRs
#20394: feat(gateway): make chat history byte limit configurable via gatewa...
by mgratch · 2026-02-18
92.6%
#13679: feat(gateway): make WebSocket maxPayload configurable
by andydoucet · 2026-02-10
85.4%
#6805: fix: increase WebSocket MAX_PAYLOAD_BYTES to 6MB for attachments
by cortexuvula · 2026-02-02
79.4%
#23420: Gateway: tighten WS connect schema bounds and validation
by bmendonca3 · 2026-02-22
77.9%
#8360: Gateway: cap oversized transcript entries
by halbot2010 · 2026-02-03
75.4%
#21741: fix(gateway): allow plaintext ws:// for Docker/private network addr...
by Joe3112 · 2026-02-20
74.9%
#16006: fix(gateway): reduce chat.history byte cap from 6 MB to 2 MB
by fagemx · 2026-02-14
74.5%
#23714: Gateway: add websocket ingress limits for DoS hardening
by bmendonca3 · 2026-02-22
74.0%
#6770: fix(gateway): protect host-local transport fields from config.patch
by ryx2 · 2026-02-02
74.0%
#12802: fix(gateway): default unscoped operator connections to read-only
by yubrew · 2026-02-09
73.9%