fix: configurable model name + debug logging (#21)#22
Conversation
Change hardcoded model 'claude-opus-4-5' to configurable OPENCLAW_MODEL (default: 'openclaw') matching gateway API contract since 2026.3.24. Implement DEBUG=true logging that was documented but never wired up. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Model was hardcoded to 'openclaw' which works for current gateways, but should be configurable for custom agent routing setups. Mirrors freema/openclaw-mcp#22. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
There was a problem hiding this comment.
Pull request overview
This PR addresses gateway compatibility issues by making the chat completion model configurable and by wiring up the previously documented debug logging so request/response payloads can be inspected during troubleshooting.
Changes:
- Replace hardcoded chat model with a default
openclaw, configurable viaOPENCLAW_MODELand--model/-m. - Add debug logging support (
DEBUG=true/--debug) and emit request/response diagnostics (with redaction + truncation). - Update docs and example configs to reflect the new model/debug options and compatibility guidance.
Reviewed changes
Copilot reviewed 15 out of 15 changed files in this pull request and generated 5 comments.
Show a summary per file
| File | Description |
|---|---|
| src/utils/logger.ts | Adds debug enablement and logDebug() support with sanitization. |
| src/openclaw/client.ts | Uses configurable model; adds debug logging inside request() and logs gateway error bodies. |
| src/openclaw/registry.ts | Passes the configured model through to all OpenClawClient instances. |
| src/index.ts | Wires CLI debug/model into logger + registry and logs selected startup settings. |
| src/config/constants.ts | Introduces DEFAULT_MODEL constant. |
| src/cli.ts | Adds --model/-m and --debug CLI flags, plus env var defaults. |
| src/tests/utils/logger.test.ts | Adds coverage for debug toggling and sanitization in debug logs. |
| src/tests/openclaw/client.test.ts | Updates expected default model and tests custom model usage. |
| docs/logging.md | Documents debug behavior and what is/isn’t logged at different levels. |
| docs/installation.md | Updates CLI help snippet to include --model and --debug. |
| docs/deployment.md | Adds compatibility table and troubleshooting guidance for 400s/model selection. |
| docs/configuration.md | Documents OPENCLAW_MODEL env var. |
| docker-compose.yml | Adds OPENCLAW_MODEL and DEBUG to environment section. |
| README.md | Adds OPENCLAW_MODEL to example configurations. |
| .env.example | Documents OPENCLAW_MODEL and clarifies debug behavior. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
|
||
| ### `400 Bad Request` from gateway on `openclaw_chat` | ||
|
|
||
| Gateway versions 2026.3.24+ require `model: "openclaw"` (or `"openclaw/<agentId>"`). The MCP bridge defaults to `"openclaw"` since v1.3.1. If you're using an older bridge version, upgrade or set `OPENCLAW_MODEL=openclaw`. If you need custom model routing, set `OPENCLAW_MODEL` to the value your gateway expects. |
There was a problem hiding this comment.
Docs inconsistency: this section says the bridge defaults to model: "openclaw" since v1.3.1, but the compatibility table above states the fix is present in ≥ 1.3.0, and package.json currently reports version 1.3.0. Please align the stated version(s) so users know which release contains the change.
| Gateway versions 2026.3.24+ require `model: "openclaw"` (or `"openclaw/<agentId>"`). The MCP bridge defaults to `"openclaw"` since v1.3.1. If you're using an older bridge version, upgrade or set `OPENCLAW_MODEL=openclaw`. If you need custom model routing, set `OPENCLAW_MODEL` to the value your gateway expects. | |
| Gateway versions 2026.3.24+ require `model: "openclaw"` (or `"openclaw/<agentId>"`). The MCP bridge defaults to `"openclaw"` since v1.3.0. If you're using an older bridge version, upgrade or set `OPENCLAW_MODEL=openclaw`. If you need custom model routing, set `OPENCLAW_MODEL` to the value your gateway expects. |
| logDebug(`Request: ${options.method ?? 'GET'} ${url}`); | ||
| if (options.body) { | ||
| logDebug(`Request body: ${this.truncateForLog(options.body as string)}`); | ||
| } |
There was a problem hiding this comment.
In request(), the debug message string is always constructed (including truncateForLog(...)) before calling logDebug, even when debug is disabled. This defeats the purpose of a cheap debug path and can become noticeable for large bodies. Consider adding an isDebugEnabled() (or a lazy logDebug(() => string) API) and guarding the body truncation/formatting behind it.
| if (!response.ok) { | ||
| const errorBody = await response.text(); | ||
| logDebug(`Response error (${response.status}): ${this.truncateForLog(errorBody)}`); | ||
| throw new OpenClawApiError( | ||
| `API request failed: ${response.status} ${response.statusText}`, | ||
| response.status | ||
| ); |
There was a problem hiding this comment.
request() always reads the full error response body (await response.text()) even when debug logging is disabled, and it does so without applying the existing 10MB size guard (Content-Length/body length) used for successful responses. This can add unnecessary latency/memory use on errors and allows very large error bodies to bypass the limit. Consider only reading/logging the body when debug is enabled, and applying the same MAX_RESPONSE_SIZE_BYTES checks before consuming it.
| export function logDebug(message: string): void { | ||
| if (debugEnabled) { | ||
| console.error(`[openclaw-mcp] DEBUG: ${sanitizeLogMessage(message)}`); | ||
| } |
There was a problem hiding this comment.
logDebug() currently can't be used in a way that avoids eager computation of log messages at call sites (template interpolation runs before the function sees debugEnabled). To keep debug logging truly low-overhead, consider exposing an isDebugEnabled() getter or changing logDebug to accept a callback so callers can skip expensive string building/truncation when debug is off.
| export function logDebug(message: string): void { | |
| if (debugEnabled) { | |
| console.error(`[openclaw-mcp] DEBUG: ${sanitizeLogMessage(message)}`); | |
| } | |
| export function isDebugEnabled(): boolean { | |
| return debugEnabled; | |
| } | |
| export function logDebug(message: string): void; | |
| export function logDebug(messageFactory: () => string): void; | |
| export function logDebug(messageOrFactory: string | (() => string)): void { | |
| if (!debugEnabled) { | |
| return; | |
| } | |
| const message = | |
| typeof messageOrFactory === 'function' ? messageOrFactory() : messageOrFactory; | |
| console.error(`[openclaw-mcp] DEBUG: ${sanitizeLogMessage(message)}`); |
| .option('model', { | ||
| alias: 'm', | ||
| type: 'string', | ||
| description: 'Model name for chat completions', | ||
| default: process.env.OPENCLAW_MODEL || DEFAULT_MODEL, | ||
| }) |
There was a problem hiding this comment.
The new --model / OPENCLAW_MODEL value is accepted as-is, including empty/whitespace strings (e.g., OPENCLAW_MODEL=), which would produce requests with model: "" and likely lead to hard-to-diagnose 400s. Consider validating model after parsing (non-empty, trimmed) and failing fast with a clear error message.
Summary
model: 'claude-opus-4-5'→ default'openclaw'(configurable viaOPENCLAW_MODELenv var /--modelCLI flag)DEBUG=truelogging that was documented but never wired up — now logs request/response bodies for troubleshooting (credentials redacted, headers never logged, bodies truncated to 4096 chars)Closes #21
Changes
src/openclaw/client.ts— configurable model param, debug logging inrequest()methodsrc/utils/logger.ts—setDebugEnabled()+logDebug()functionssrc/cli.ts—--model/-mand--debugCLI options (respectsDEBUG=trueandNODE_ENV=development)src/openclaw/registry.ts— passes model to all client instancessrc/index.ts— wires debug + model through registry.env.example,docker-compose.ymlupdatedTest plan
npm run typecheck— 0 errorsnpm run test:run— 136/136 passednpm run lint— cleannpm run build— success