Conversation info (untrusted metadata):
```json
{
  "message_id": "2306",
  "sender_id": "8340647700",
  "sender": "8340647700"
}
```

Two tasks this morning: 1. Fix model routing to Sonnet 4.6: Research the correct modelByChannel syntax for OpenClaw 2026.2.21-2 before editing anything. Last night’s attempt crashed the gateway because the format was wrong. Check the OpenClaw docs or release notes for the correct schema. If you can’t find the right format, ask me first instead of guessing. Validate the JSON AND confirm the gateway stays running before considering it done. 2. Update session health monitor thresholds for the new 1M token context window: ∙ ⚪ OK: under 300K tokens ∙ 🟡 Elevated: 300K-500K tokens ∙ 🟠 Warning: 500K-750K tokens ∙ 🔴 Critical: over 750K tokens Do NOT restart the gateway until you’re confident the config is valid. Report back when done.


**Preparing to run memory search**

Question: model routing

Question: model routing


**Planning research on OpenClaw docs**


ACTION-BOARD-DESIGN.md
ACTION-BOARD-IMPLEMENTATION-SUMMARY.md
ACTION-BOARD-QUICKSTART.md
ACTION-BOARD-VISUAL-EXAMPLE.md
AGENTS.md
BluePrism-Dominik-Email-Draft.pdf
DASHBOARD-KANBAN-DELIVERABLES.md
DASHBOARD-KANBAN-EXECUTIVE-SUMMARY.md
DASHBOARD-SERVER-QUICKSTART.md
DASHBOARD-WORKFLOW-COMPARISON.md
Dashboard.url
Episode1.m4a
Episode1_first60.wav
Episode1_normalized.mp3
Episode1_roomfix_v1.mp3
Episode1_studio.mp3
Episode1_transcript.txt
Episode1_work.wav
HEARTBEAT.md
IDEAS.md
IDENTITY.md
Kanban.url
MEMORY.md
Non-Employee_IT_Access_Form.pdf
Non-Employee_IT_Access_Form_updated.pdf
README-DASHBOARD-FIX.md
SHOPIFY-IDEAS.md
SOUL.md
SUBAGENT-COMPLETION-REPORT.md
TOOLS.md
USER.md
WORKFLOW_AUTO.md
__pycache__
action
audio-tools
backups
bin
blueprism
blueprism-dominik-email-draft.roff
calendar
claude_shopify.md
dashboard
dashboard-kanban-review.md
docs
form-1.png
form_bbox.html
forms
gpt_shopify.md
grok_shopify.md
kanban
memory
out.txt
podcast
portal
presidents-day-ooo-8k-mono.wav
scripts
secrets
serve.py
start-server.sh
stop-server.sh
tmp_laura_reply_draft.txt
tmp_research_brief.txt
tmp_toassistant.code
tmp_toassistant.out
tmp_verizon_thread.txt
transcripts
updated.txt
updated_form-1.png
watch-and-rebuild.sh


**Searching for modelByChannel references**


/bin/bash: line 1: rg: command not found

(Command exited with code 127)


**Choosing alternative search tool**


memory/2026-02-22.md:2:- 00:35 EST: Pinned Telegram DM (chat 8340647700) to **claude-sonnet-4-6** via `channels.modelByChannel` in openclaw.json so live conversations run on the 1M ctx model while background/cron tasks continue to default to GPT-5.2.
backups/sessions/main_telegram_session_5103f7ad-76f2-4bad-b612-0df5cb1f0474_20260222T0015EST.jsonl:628:{"type":"message","id":"f4e960f7","parentId":"0fe0b86c","timestamp":"2026-02-22T05:32:23.929Z","message":{"role":"toolResult","toolCallId":"call_zqQincPmzp4lDnEKkkYaTb3y|fc_01dbf2355da2cb0600699a94e3d00c8195a84a33f88c5a6393","toolName":"web_search","content":[{"type":"text","text":"{\n  \"query\": \"OpenClaw config per-channel model override telegram\",\n  \"provider\": \"perplexity\",\n  \"model\": \"sonar\",\n  \"tookMs\": 3034,\n  \"externalContent\": {\n    \"untrusted\": true,\n    \"source\": \"web_search\",\n    \"provider\": \"perplexity\",\n    \"wrapped\": true\n  },\n  \"content\": \"\\n<<<EXTERNAL_UNTRUSTED_CONTENT id=\\\"34d02e4a70cb7d89\\\">>>\\nSource: Web Search\\n---\\n**OpenClaw supports per-channel model overrides via the `channels.modelByChannel` configuration in `openclaw.json`.** This pins specific channel IDs (e.g., Telegram chat IDs) to a model using `provider/model` syntax or aliases, applying only when no session-level override (like `/model`) is set.[1]\\n\\n### Configuration Example\\nAdd this under the `channels` section:\\n```\\nchannels: {\\n  modelByChannel: {\\n    \\\"telegram:123456789\\\": \\\"anthropic/claude-sonnet-4-5\\\",  // Telegram channel ID → model\\n    \\\"telegram:987654321\\\": \\\"openai/gpt-4o\\\"                // Another channel → different model\\n  }\\n}\\n```\\n- Use full `provider/model` or aliases (e.g., \\\"Sonnet\\\" if defined).[1]\\n- Resolution prioritizes session > channel > global; supports `alias` and `params` (e.g., `temperature`, `maxTokens`).[1]\\n- For Telegram groups, extend to `channels.telegram.groups.<id>.model` (feature discussed).[3]\\n- Changes require gateway restart: `openclaw gateway restart`.[2]\\n\\n### Related Overrides\\n- **Per-account/channel response prefix**: `channels.<channel>.responsePrefix` or `channels.<channel>.accounts.<id>.responsePrefix` (most specific wins).[1]\\n- **Agent mapping**: Advanced routing like `\\\"dm:telegram:*\\\": { \\\"model\\\": \\\"openai/gpt-4o\\\" }`.[5]\\n- Global models: Set `agents.defaults.model.primary` and fallbacks first.[4]\\n\\nEdit `openclaw.json` with `vim openclaw.json` or similar, save, and restart.[2] For full reference, see OpenClaw docs.[1][4]\\n<<<END_EXTERNAL_UNTRUSTED_CONTENT id=\\\"34d02e4a70cb7d89\\\">>>\",\n  \"citations\": [\n    \"https://docs.openclaw.ai/gateway/configuration-reference\",\n    \"https://www.youtube.com/watch?v=Btr0KKvrack\",\n    \"https://github.com/openclaw/openclaw/issues/17781\",\n    \"https://docs.openclaw.ai/gateway/configuration\",\n    \"https://ppaolo.substack.com/p/openclaw-system-architecture-overview\",\n    \"https://cloudsecurityalliance.org/blog/2026/02/20/openclaw-threat-model-maestro-framework-analysis\",\n    \"https://www.youtube.com/watch?v=fcZMmP5dsl4\",\n    \"https://github.com/openclaw/openclaw/issues/12246\"\n  ]\n}"}],"details":{"query":"OpenClaw config per-channel model override telegram","provider":"perplexity","model":"sonar","tookMs":3034,"externalContent":{"untrusted":true,"source":"web_search","provider":"perplexity","wrapped":true},"content":"\n<<<EXTERNAL_UNTRUSTED_CONTENT id=\"34d02e4a70cb7d89\">>>\nSource: Web Search\n---\n**OpenClaw supports per-channel model overrides via the `channels.modelByChannel` configuration in `openclaw.json`.** This pins specific channel IDs (e.g., Telegram chat IDs) to a model using `provider/model` syntax or aliases, applying only when no session-level override (like `/model`) is set.[1]\n\n### Configuration Example\nAdd this under the `channels` section:\n```\nchannels: {\n  modelByChannel: {\n    \"telegram:123456789\": \"anthropic/claude-sonnet-4-5\",  // Telegram channel ID → model\n    \"telegram:987654321\": \"openai/gpt-4o\"                // Another channel → different model\n  }\n}\n```\n- Use full `provider/model` or aliases (e.g., \"Sonnet\" if defined).[1]\n- Resolution prioritizes session > channel > global; supports `alias` and `params` (e.g., `temperature`, `maxTokens`).[1]\n- For Telegram groups, extend to `channels.telegram.groups.<id>.model` (feature discussed).[3]\n- Changes require gateway restart: `openclaw gateway restart`.[2]\n\n### Related Overrides\n- **Per-account/channel response prefix**: `channels.<channel>.responsePrefix` or `channels.<channel>.accounts.<id>.responsePrefix` (most specific wins).[1]\n- **Agent mapping**: Advanced routing like `\"dm:telegram:*\": { \"model\": \"openai/gpt-4o\" }`.[5]\n- Global models: Set `agents.defaults.model.primary` and fallbacks first.[4]\n\nEdit `openclaw.json` with `vim openclaw.json` or similar, save, and restart.[2] For full reference, see OpenClaw docs.[1][4]\n<<<END_EXTERNAL_UNTRUSTED_CONTENT id=\"34d02e4a70cb7d89\">>>","citations":["https://docs.openclaw.ai/gateway/configuration-reference","https://www.youtube.com/watch?v=Btr0KKvrack","https://github.com/openclaw/openclaw/issues/17781","https://docs.openclaw.ai/gateway/configuration","https://ppaolo.substack.com/p/openclaw-system-architecture-overview","https://cloudsecurityalliance.org/blog/2026/02/20/openclaw-threat-model-maestro-framework-analysis","https://www.youtube.com/watch?v=fcZMmP5dsl4","https://github.com/openclaw/openclaw/issues/12246"]},"isError":false,"timestamp":1771738343921}}
backups/sessions/main_telegram_session_5103f7ad-76f2-4bad-b612-0df5cb1f0474_20260222T0015EST.jsonl:637:{"type":"message","id":"2d90db3f","parentId":"f1d66261","timestamp":"2026-02-22T05:33:15.539Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"","thinkingSignature":"{\"id\":\"rs_01dbf2355da2cb0600699a950f87808195a477caaed4d6ab43\",\"type\":\"reasoning\",\"summary\":[]}"},{"type":"toolCall","id":"call_j3aDor4lurqJ0zJvN2HlWAAm|fc_01dbf2355da2cb0600699a9519c24c8195b1679cbd1a868d4b","name":"exec","arguments":{"command":"python3 - <<'PY'\nimport json, pathlib\npath = pathlib.Path('/home/isthekid/.openclaw/openclaw.json')\nwith path.open() as f:\n    data = json.load(f)\nchannels = data.setdefault('channels', {})\nmodel_map = channels.get('modelByChannel')\nif model_map is None or not isinstance(model_map, dict):\n    model_map = {}\nchannels['modelByChannel'] = model_map\nif model_map.get('telegram:8340647700') != 'anthropic/claude-sonnet-4-6':\n    model_map['telegram:8340647700'] = 'anthropic/claude-sonnet-4-6'\npath.write_text(json.dumps(data, indent=2) + '\\n')\nPY","timeout":120,"pty":true},"partialJson":"{\"command\":\"python3 - <<'PY'\\nimport json, pathlib\\npath = pathlib.Path('/home/isthekid/.openclaw/openclaw.json')\\nwith path.open() as f:\\n    data = json.load(f)\\nchannels = data.setdefault('channels', {})\\nmodel_map = channels.get('modelByChannel')\\nif model_map is None or not isinstance(model_map, dict):\\n    model_map = {}\\nchannels['modelByChannel'] = model_map\\nif model_map.get('telegram:8340647700') != 'anthropic/claude-sonnet-4-6':\\n    model_map['telegram:8340647700'] = 'anthropic/claude-sonnet-4-6'\\npath.write_text(json.dumps(data, indent=2) + '\\\\n')\\nPY\",\"timeout\":120,\"pty\":true}"}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":8526,"output":911,"cacheRead":127616,"cacheWrite":0,"totalTokens":137053,"cost":{"input":0.0106575,"output":0.00911,"cacheRead":0.015952,"cacheWrite":0,"total":0.0357195}},"stopReason":"toolUse","timestamp":1771738381267}}
backups/sessions/main_telegram_session_5103f7ad-76f2-4bad-b612-0df5cb1f0474_20260222T0015EST.jsonl:639:{"type":"message","id":"c3c4840f","parentId":"57973ced","timestamp":"2026-02-22T05:33:19.706Z","message":{"role":"assistant","content":[{"type":"thinking","thinking":"","thinkingSignature":"{\"id\":\"rs_01dbf2355da2cb0600699a951e42c0819587674b78587df966\",\"type\":\"reasoning\",\"summary\":[]}"},{"type":"toolCall","id":"call_nWyDNhJTkTxp2CPFOi3CZ8Gp|fc_01dbf2355da2cb0600699a951e7f0c8195bb1cce6919ccfdde","name":"exec","arguments":{"command":"python3 - <<'PY'\nimport json\nfrom pathlib import Path\npath = Path('/home/isthekid/.openclaw/openclaw.json')\ndata = json.load(path.open())\nprint(json.dumps(data['channels'].get('modelByChannel'), indent=2))\nPY","timeout":120,"pty":true},"partialJson":"{\"command\":\"python3 - <<'PY'\\nimport json\\nfrom pathlib import Path\\npath = Path('/home/isthekid/.openclaw/openclaw.json')\\ndata = json.load(path.open())\\nprint(json.dumps(data['channels'].get('modelByChannel'), indent=2))\\nPY\",\"timeout\":120,\"pty\":true}"}],"api":"openai-responses","provider":"openai","model":"gpt-5.1-codex","usage":{"input":1514,"output":105,"cacheRead":135552,"cacheWrite":0,"totalTokens":137171,"cost":{"input":0.0018925,"output":0.0010500000000000002,"cacheRead":0.016944,"cacheWrite":0,"total":0.0198865}},"stopReason":"toolUse","timestamp":1771738395615}}


**Searching documentation for modelByChannel**


(Command exited with code 1)


**Checking for document matches**


gmail-intake-playbook.md


**Preparing to research modelByChannel syntax**

Question: OpenClaw 2026.2.21-2 modelByChannel syntax

Question: OpenClaw 2026.2.21-2 modelByChannel syntax


Question: OpenClaw 2026.2.21-2 modelByChannel syntax

<<<EXTERNAL_UNTRUSTED_CONTENT id="0b929ff954f7ac67">>>
Source: Web Search
---
**No `modelByChannel` syntax is documented in OpenClaw 2026.2.21-2.**[1][2][3]

Model configuration in OpenClaw is handled globally via `agents.defaults.model.primary` (primary model) and `agents.defaults.model.fallbacks` (ordered fallbacks), applied across all channels and agents unless overridden per-agent (e.g., in `~/.openclaw/agents/<agentId>/models.json`, merged by default).[3][4][5]

- **CLI management**: Use `openclaw models set <provider/model>` to set the primary model, `models list` (with flags like `--provider`, `--json`) to view options, or `models status` for details including auth probes.[3][5]
- **No per-channel model routing**: Configuration examples show channel-specific settings (e.g., `channels.telegram.enabled`, `dmPolicy`, `allowFrom`, `groups`), but models are selected in fixed order without channel-based overrides.[1][2]
- **Provider failover**: Occurs within a provider before fallback models; use `/model` or `/models` commands in chats for interactive selection.[3]

For channel setups like Telegram or WhatsApp, define under `channels` (e.g., `channels: { telegram: { botToken: "123:abc", dmPolicy: "pairing" } }`), but pair with global models.[1][2] Check full config reference or run `openclaw onboard` for interactive setup; update via `openclaw gateway call config.patch` for patches.[2][7] If this refers to a custom or unreleased feature, consult OpenClaw GitHub issues or run `openclaw models --help` locally.[1][5]
<<<END_EXTERNAL_UNTRUSTED_CONTENT id="0b929ff954f7ac67">>>

Question: OpenClaw 2026.2.21-2 modelByChannel syntax


**Verifying modelByChannel documentation**

Question: "modelByChannel" "OpenClaw"

Question: "modelByChannel" "OpenClaw"


Question: "modelByChannel" "OpenClaw"

<<<EXTERNAL_UNTRUSTED_CONTENT id="745448924d7dab44">>>
Source: Web Search
---
**`channels.modelByChannel`** is a configuration option in OpenClaw that enables per-channel overrides for AI models, allowing different models to be specified and used for specific communication channels (e.g., Discord, Telegram).[1][2]

This feature notes the overrides in the `/status` endpoint for visibility. It was added in a recent release, with thanks to contributor @thewilloftheshadow.[1][2]

### Usage Context
- OpenClaw is an open-source AI assistant platform that connects to services via "skills," runs autonomous agents/jobs, and routes interactions across channels like messaging apps.[3][5]
- Configuration is typically set in YAML files or CLI flags, where `channels.modelByChannel` maps channel IDs/names to model providers (e.g., overriding global defaults for context windows or providers like OpenAI/Anthropic).[1][2][4]
- Related features include per-channel routing fallbacks (`defaultTo`), session continuity, and diagnostics.[1][2]

No exact code examples are in the results, but it integrates with agent context handling and provider discovery.[2] For setup, refer to OpenClaw's GitHub releases or docs.[1][2][4]
<<<END_EXTERNAL_UNTRUSTED_CONTENT id="745448924d7dab44">>>

Question: "modelByChannel" "OpenClaw"


**Planning to access release notes**


Can't reach the OpenClaw browser control service. Restart the OpenClaw gateway (OpenClaw.app menubar, or `openclaw gateway`). Do NOT retry the browser tool — it will keep failing. Use an alternative approach or inform the user that the browser is currently unavailable. (Error: Error: No supported browser found (Chrome/Brave/Edge/Chromium on macOS, Linux, or Windows).)


**Planning alternate download method**

exec

curl -sL https://raw.githubusercontent.com/openclaw/openclaw/v2026.2.21-2/CHANGELOG.md | head


404: Not Found


**Verifying release tag naming**

curl -sL https://raw.githubusercontent.com/openclaw/openclaw/main/CHANGELOG.md | head -n 200


# Changelog

Docs: https://docs.openclaw.ai

## 2026.2.22 (Unreleased)

### Changes

- Skills: remove bundled `food-order` skill from this repo; manage/install it from ClawHub instead.
- Docs/Subagents: make thread-bound session guidance channel-first instead of Discord-specific, and list thread-supporting channels explicitly. (#23589) Thanks @osolmaz.
- Channels/Config: unify channel preview streaming config handling with a shared resolver and canonical migration path.
- Discord/Allowlist: canonicalize resolved Discord allowlist names to IDs and split resolution flow for clearer fail-closed behavior.
- Memory/FTS: add Korean stop-word filtering and particle-aware keyword extraction (including mixed Korean/English stems) for query expansion in FTS-only search mode. (#18899) Thanks @ruypang.
- iOS/Talk: prefetch TTS segments and suppress expected speech-cancellation errors for smoother talk playback. (#22833) Thanks @ngutman.

### Breaking

- **BREAKING:** remove legacy Gateway device-auth signature `v1`. Device-auth clients must now sign `v2` payloads with the per-connection `connect.challenge` nonce and send `device.nonce`; nonce-less connects are rejected.
- **BREAKING:** unify channel preview-streaming config to `channels.<channel>.streaming` with enum values `off | partial | block | progress`, and move Slack native stream toggle to `channels.slack.nativeStreaming`. Legacy keys (`streamMode`, Slack boolean `streaming`) are still read and migrated by `openclaw doctor --fix`, but canonical saved config/docs now use the unified names.
- **BREAKING:** CLI local onboarding now sets `session.dmScope` to `per-channel-peer` by default for new/implicit DM scope configuration. If you depend on shared DM continuity across senders, explicitly set `session.dmScope` to `main`. (#23468) Thanks @bmendonca3.
- **BREAKING:** tool-failure replies now hide raw error details by default. OpenClaw still sends a failure summary, but detailed error suffixes (for example provider/runtime messages and local path fragments) now require `/verbose on` or `/verbose full`.

### Fixes

- Signal/RPC: guard malformed Signal RPC JSON responses with a clear status-scoped error and add regression coverage for invalid JSON responses. (#22995) Thanks @adhitShet.
- Gateway/Subagents: guard gateway and subagent session-key/message trim paths against undefined inputs to prevent early `Cannot read properties of undefined (reading 'trim')` crashes during subagent spawn and wait flows.
- Agents/Workspace: guard `resolveUserPath` against undefined/null input to prevent `Cannot read properties of undefined (reading 'trim')` crashes when workspace paths are missing in embedded runner flows.
- Auth/Profiles: keep active `cooldownUntil`/`disabledUntil` windows immutable across retries so mid-window failures cannot extend recovery indefinitely; only recompute a backoff window after the previous deadline has expired. This resolves cron/inbound retry loops that could trap gateways until manual `usageStats` cleanup. (#23516, #23536) Thanks @arosstale.
- Channels/Security: fail closed on missing provider group policy config by defaulting runtime group policy to `allowlist` (instead of inheriting `channels.defaults.groupPolicy`) when `channels.<provider>` is absent across message channels, and align runtime + security warnings/docs to the same fallback behavior (Slack, Discord, iMessage, Telegram, WhatsApp, Signal, LINE, Matrix, Mattermost, Google Chat, IRC, Nextcloud Talk, Feishu, and Zalo user flows; plus Discord message/native-command paths). (#23367) Thanks @bmendonca3.
- Gateway/Onboarding: harden remote gateway onboarding defaults and guidance by defaulting discovered direct URLs to `wss://`, rejecting insecure non-loopback `ws://` targets in onboarding validation, and expanding remote-security remediation messaging across gateway client/call/doctor flows. (#23476) Thanks @bmendonca3.
- CLI/Sessions: pass the configured sessions directory when resolving transcript paths in `agentCommand`, so custom `session.store` locations resume sessions reliably. Thanks @davidrudduck.
- Gateway/Chat UI: strip inline reply/audio directive tags from non-streaming final webchat broadcasts (including `chat.inject`) while preserving empty-string message content when tags are the entire reply. (#23298) Thanks @SidQin-cyber.
- Gateway/Restart: fix restart-loop edge cases by keeping `openclaw.mjs -> dist/entry.js` bootstrap detection explicit, reacquiring the gateway lock for in-process restart fallback paths, and tightening restart-loop regression coverage. (#23416) Thanks @jeffwnli.
- Signal/Monitor: treat user-initiated abort shutdowns as clean exits when auto-started `signal-cli` is terminated, while still surfacing unexpected daemon exits as startup/runtime failures. (#23379) Thanks @frankekn.
- Channels/Dedupe: centralize plugin dedupe primitives in plugin SDK (memory + persistent), move Feishu inbound dedupe to a namespace-scoped persistent store, and reuse shared dedupe cache logic for Zalo webhook replay + Tlon processed-message tracking to reduce duplicate handling during reconnect/replay paths. (#23377) Thanks @SidQin-cyber.
- Channels/Delivery: remove hardcoded WhatsApp delivery fallbacks; require explicit/session channel context or auto-pick the sole configured channel when unambiguous. (#23357) Thanks @lbo728.
- ACP/Gateway: wait for gateway hello before opening ACP requests, and fail fast on pre-hello connect failures to avoid startup hangs and early `gateway not connected` request races. (#23390) Thanks @janckerchen.
- Security/Audit: add `openclaw security audit` detection for open group policies that expose runtime/filesystem tools without sandbox/workspace guards (`security.exposure.open_groups_with_runtime_or_fs`).
- Security/Audit: make `gateway.real_ip_fallback_enabled` severity conditional for loopback trusted-proxy setups (warn for loopback-only `trustedProxies`, critical when non-loopback proxies are trusted). (#23428) Thanks @bmendonca3.
- Security/Exec env: block request-scoped `HOME` and `ZDOTDIR` overrides in host exec env sanitizers (Node + macOS), preventing shell startup-file execution before allowlist-evaluated command bodies. This ships in the next npm release. Thanks @tdjackey for reporting.
- Security/Exec env: block `SHELLOPTS`/`PS4` in host exec env sanitizers and restrict shell-wrapper (`bash|sh|zsh ... -c/-lc`) request env overrides to a small explicit allowlist (`TERM`, `LANG`, `LC_*`, `COLORTERM`, `NO_COLOR`, `FORCE_COLOR`) on both node host and macOS companion paths, preventing xtrace prompt command-substitution allowlist bypasses. This ships in the next npm release. Thanks @tdjackey for reporting.
- Security/Gateway: emit a startup security warning when insecure/dangerous config flags are enabled (including `gateway.controlUi.dangerouslyDisableDeviceAuth=true`) and point operators to `openclaw security audit`.
- Security/Hooks auth: normalize hook auth rate-limit client IP keys so IPv4 and IPv4-mapped IPv6 addresses share one throttle bucket, preventing dual-form auth-attempt budget bypasses. This ships in the next npm release. Thanks @aether-ai-agent for reporting.
- Security/Exec approvals: treat `env` and shell-dispatch wrappers as transparent during allowlist analysis on node-host and macOS companion paths so policy checks match the effective executable/inline shell payload instead of the wrapper binary, blocking wrapper-smuggled allowlist bypasses. This ships in the next npm release. Thanks @tdjackey for reporting.
- Security/Exec approvals: require explicit safe-bin profiles for `tools.exec.safeBins` entries in allowlist mode (remove generic safe-bin profile fallback), and add `tools.exec.safeBinProfiles` for safe custom binaries so unprofiled interpreter-style entries cannot be treated as stdin-safe. This ships in the next npm release. Thanks @tdjackey for reporting.
- Security/Channels: harden Slack external menu token handling by switching to CSPRNG tokens, validating token shape, requiring user identity for external option lookups, and avoiding fabricated timestamp `trigger_id` fallbacks; also switch Tlon Urbit channel IDs to CSPRNG UUIDs, centralize secure ID/token generation via shared infra helpers, and add a guardrail test to block new runtime `Date.now()+Math.random()` token/id patterns.
- Security/Hooks transforms: enforce symlink-safe containment for webhook transform module paths (including `hooks.transformsDir` and `hooks.mappings[].transform.module`) by resolving existing-path ancestors via realpath before import, while preserving in-root symlink support; add regression coverage for both escape and allow cases. This ships in the next npm release. Thanks @aether-ai-agent for reporting.
- Telegram/WSL2: disable `autoSelectFamily` by default on WSL2 and memoize WSL2 detection in Telegram network decision logic to avoid repeated sync `/proc/version` probes on fetch/send paths. (#21916) Thanks @MizukiMachine.
- Telegram/Network: default Node 22+ DNS result ordering to `ipv4first` for Telegram fetch paths and add `OPENCLAW_TELEGRAM_DNS_RESULT_ORDER`/`channels.telegram.network.dnsResultOrder` overrides to reduce IPv6-path fetch failures. (#5405) Thanks @Glucksberg.
- Telegram/Streaming: preserve archived draft preview mapping after flush and clean superseded reasoning preview bubbles so multi-message preview finals no longer cross-edit or orphan stale messages under send/rotation races. (#23202) Thanks @obviyus.
- Telegram/Polling: persist a safe update-offset watermark bounded by pending updates so crash/restart cannot skip queued lower `update_id` updates after out-of-order completion. (#23284) thanks @frankekn.
- Slack/Slash commands: preserve the Bolt app receiver when registering external select options handlers so monitor startup does not crash on runtimes that require bound `app.options` calls. (#23209) Thanks @0xgaia.
- Slack/Telegram slash sessions: await session metadata persistence before dispatch so first-turn native slash runs do not race session-origin metadata updates. (#23065) thanks @hydro13.
- Agents/Ollama: preserve unsafe integer tool-call arguments as exact strings during NDJSON parsing, preventing large numeric IDs from being rounded before tool execution. (#23170) Thanks @BestJoester.
- Cron/Gateway: keep `cron.list` and `cron.status` responsive during startup catch-up by avoiding a long-held cron lock while missed jobs execute. (#23106) Thanks @jayleekr.
- Gateway/Config reload: compare array-valued config paths structurally during diffing so unchanged `memory.qmd.paths` and `memory.qmd.scope.rules` no longer trigger false restart-required reloads. (#23185) Thanks @rex05ai.
- Gateway/Config reload: retry short-lived missing config snapshots during reload before skipping, preventing atomic-write unlink windows from triggering restart loops. (#23343) Thanks @lbo728.
- Cron/Scheduling: validate runtime cron expressions before schedule/stagger evaluation so malformed persisted jobs report a clear `invalid cron schedule: expr is required` error instead of crashing with `undefined.trim` failures and auto-disable churn. (#23223) Thanks @asimons81.
- Memory/QMD: migrate legacy unscoped collection bindings (for example `memory-root`) to per-agent scoped names (for example `memory-root-main`) during startup when safe, so QMD-backed `memory_search` no longer fails with `Collection not found` after upgrades. (#23228, #20727) Thanks @JLDynamics and @AaronFaby.
- Memory/QMD: normalize Han-script BM25 search queries before invoking `qmd search` so mixed CJK+Latin prompts no longer return empty results due to tokenizer mismatch. (#23426) Thanks @LunaLee0130.
- TUI/Input: enable multiline-paste burst coalescing on macOS Terminal.app and iTerm so pasted blocks no longer submit line-by-line as separate messages. (#18809) Thanks @fwends.
- TUI/RTL: isolate right-to-left script lines (Arabic/Hebrew ranges) with Unicode bidi isolation marks in TUI text sanitization so RTL assistant output no longer renders in reversed visual order in terminal chat panes. (#21936) Thanks @Asm3r96.
- TUI/Status: request immediate renders after setting `sending`/`waiting` activity states so in-flight runs always show visible progress indicators instead of appearing idle until completion. (#21549) Thanks @13Guinness.
- TUI/Input: arm Ctrl+C exit timing when clearing non-empty composer text and add a SIGINT fallback path so double Ctrl+C exits remain responsive during active runs instead of requiring an extra press or appearing stuck. (#23407) Thanks @tinybluedev.
- Agents/Fallbacks: treat JSON payloads with `type: "api_error"` + `"Internal server error"` as transient failover errors so Anthropic 500-style failures trigger model fallback. (#23193) Thanks @jarvis-lane.
- Agents/Google: sanitize non-base64 `thought_signature`/`thoughtSignature` values from assistant replay transcripts for native Google Gemini requests while preserving valid signatures and tool-call order. (#23457) Thanks @echoVic.
- Agents/Transcripts: validate assistant tool-call names (syntax/length + registered tool allowlist) before transcript persistence and during replay sanitization so malformed failover tool names no longer poison sessions with repeated provider HTTP 400 errors. (#23324) Thanks @johnsantry.
- Agents/Compaction: strip stale assistant usage snapshots from pre-compaction turns when replaying history after a compaction summary so context-token estimation no longer reuses pre-compaction totals and immediately re-triggers destructive follow-up compactions. (#19127) Thanks @tedwatson.
- Agents/Replies: emit a default completion acknowledgement (`✅ Done.`) when runs execute tools successfully but return no final assistant text, preventing silent no-reply turns after tool-only completions. (#22834) Thanks @Oldshue.
- Agents/Subagents: honor `tools.subagents.tools.alsoAllow` and explicit subagent `allow` entries when resolving built-in subagent deny defaults, so explicitly granted tools (for example `sessions_send`) are no longer blocked unless re-denied in `tools.subagents.tools.deny`. (#23359) Thanks @goren-beehero.
- Agents/Diagnostics: include resolved lifecycle error text in `embedded run agent end` warnings so UI/TUI “Connection error” runs expose actionable provider failure reasons in gateway logs. (#23054) Thanks @Raize.
- Agents/Auth profiles: skip auth-profile cooldown writes for timeout failures in embedded runner rotation so model/network timeouts do not poison same-provider fallback model selection while still allowing in-turn account rotation. (#22622) Thanks @vageeshkumar.
- Plugins/Hooks: run legacy `before_agent_start` once per agent turn and reuse that result across model-resolve and prompt-build compatibility paths, preventing duplicate hook side effects (for example duplicate external API calls). (#23289) Thanks @ksato8710.
- Models/Config: default missing Anthropic provider/model `api` fields to `anthropic-messages` during config validation so custom relay model entries are preserved instead of being dropped by runtime model registry validation. (#23332) Thanks @bigbigmonkey123.
- Gateway/Pairing: treat operator.admin pairing tokens as satisfying operator.write requests so legacy devices stop looping through scope-upgrade prompts introduced in 2026.2.19. (#23125, #23006) Thanks @vignesh07.
- Gateway/Pairing: treat `operator.admin` as satisfying other `operator.*` scope checks during device-auth verification so local CLI/TUI sessions stop entering pairing-required loops for pairing/approval-scoped commands. (#22062, #22193, #21191) Thanks @Botaccess, @jhartshorn, and @ctbritt.
- Gateway/Pairing: preserve existing approved token scopes when processing repair pairings that omit `scopes`, preventing empty-scope token regressions on reconnecting clients. (#21906) Thanks @paki81.
- Gateway/Scopes: include `operator.read` and `operator.write` in default operator connect scope bundles across CLI, Control UI, and macOS clients so write-scoped announce/sub-agent follow-up calls no longer hit `pairing required` disconnects on loopback gateways. (#22582) thanks @YuzuruS.
- Plugins/CLI: make `openclaw plugins enable` and plugin install/link flows update allowlists via shared plugin-enable policy so enabled plugins are not left disabled by allowlist mismatch. (#23190) Thanks @downwind7clawd-ctrl.
- Memory/QMD: add optional `memory.qmd.mcporter` search routing so QMD `query/search/vsearch` can run through mcporter keep-alive flows (including multi-collection paths) to reduce cold starts, while keeping searches on agent-scoped QMD state for consistent recall. (#19617) Thanks @nicole-luxe and @vignesh07.
- Chat/UI: strip inline reply/audio directive tags (`[[reply_to_current]]`, `[[reply_to:<id>]]`, `[[audio_as_voice]]`) from displayed chat history, live chat event output, and session preview snippets so control tags no longer leak into user-visible surfaces.
- Telegram/Retry: classify undici `TypeError: fetch failed` as recoverable in both polling and send retry paths so transient fetch failures no longer fail fast. (#16699) thanks @Glucksberg.
- BlueBubbles/DM history: restore DM backfill context with account-scoped rolling history, bounded backfill retries, and safer history payload limits. (#20302) Thanks @Ryan-Haines.
- BlueBubbles/Private API cache: treat unknown (`null`) private-API cache status as disabled for send/attachment/reply flows to avoid stale-cache 500s, and log a warning when reply/effect features are requested while capability is unknown. (#23459) Thanks @echoVic.
- BlueBubbles/Webhooks: accept inbound/reaction webhook payloads when BlueBubbles omits `handle` but provides DM `chatGuid`, and harden payload extraction for array/string-wrapped message bodies so valid webhook events no longer get rejected as unparseable. (#23275) Thanks @toph31.
- Security/Audit: add `openclaw security audit` finding `gateway.nodes.allow_commands_dangerous` for risky `gateway.nodes.allowCommands` overrides, with severity upgraded to critical on remote gateway exposure.
- Gateway/Control plane: reduce cross-client write limiter contention by adding `connId` fallback keying when device ID and client IP are both unavailable.
- Security/Config: block prototype-key traversal during config merge patch and legacy migration merge helpers (`__proto__`, `constructor`, `prototype`) to prevent prototype pollution during config mutation flows. (#22968) Thanks @Clawborn.
- Security/Shell env: validate login-shell executable paths for shell-env fallback (`/etc/shells` + trusted prefixes) and block `SHELL` in dangerous env override policy paths so untrusted shell-path injection falls back safely to `/bin/sh`. Thanks @athuljayaram for reporting.
- Security/Config: make parsed chat allowlist checks fail closed when `allowFrom` is empty, restoring expected DM/pairing gating.
- Security/Exec: in non-default setups that manually add `sort` to `tools.exec.safeBins`, block `sort --compress-program` so allowlist-mode safe-bin checks cannot bypass approval. Thanks @tdjackey for reporting.
- Security/Exec approvals: when users choose `allow-always` for shell-wrapper commands (for example `/bin/zsh -lc ...`), persist allowlist patterns for the inner executable(s) instead of the wrapper shell binary, preventing accidental broad shell allowlisting in moderate mode. (#23276) Thanks @xrom2863.
- Security/Exec: fail closed when `tools.exec.host=sandbox` is configured/requested but sandbox runtime is unavailable, and default implicit exec host routing to `gateway` when no sandbox runtime exists. (#23398) Thanks @bmendonca3.
- Security/macOS app beta: enforce path-only `system.run` allowlist matching (drop basename matches like `echo`), migrate legacy basename entries to last resolved paths when available, and harden shell-chain handling to fail closed on unsafe parse/control syntax (including quoted command substitution/backticks). This is an optional allowlist-mode feature; default installs remain deny-by-default. This ships in the next npm release. Thanks @tdjackey for reporting.
- Security/Agents: auto-generate and persist a dedicated `commands.ownerDisplaySecret` when `commands.ownerDisplay=hash`, remove gateway token fallback from owner-ID prompt hashing across CLI and embedded agent runners, and centralize owner-display secret resolution in one shared helper. This ships in the next npm release. Thanks @aether-ai-agent for reporting.
- Security/SSRF: expand IPv4 fetch guard blocking to include RFC special-use/non-global ranges (including benchmarking, TEST-NET, multicast, and reserved/broadcast blocks), and centralize range checks into a single CIDR policy table to reduce classifier drift.
- Security/Archive: block zip symlink escapes during archive extraction.
- Security/Media sandbox: keep tmp media allowance for absolute tmp paths only and enforce symlink-escape checks before sandbox-validated reads, preventing tmp symlink exfiltration and relative `../` sandbox escapes when sandboxes live under tmp. (#17892) Thanks @dashed.
- Browser/Upload: accept canonical in-root upload paths when the configured uploads directory is a symlink alias (for example `/tmp` -> `/private/tmp` on macOS), so browser upload validation no longer rejects valid files during client->server revalidation. (#23300, #23222, #22848) Thanks @bgaither4, @parkerati, and @Nabsku.
- Security/Discord: add `openclaw security audit` warnings for name/tag-based Discord allowlist entries (DM allowlists, guild/channel `users`, and pairing-store entries), highlighting slug-collision risk while keeping name-based matching supported, and canonicalize resolved Discord allowlist names to IDs at runtime without rewriting config files. Thanks @tdjackey for reporting.
- Security/Gateway: block node-role connections when device identity metadata is missing.
- Security/Media: enforce inbound media byte limits during download/read across Discord, Telegram, Zalo, Microsoft Teams, and BlueBubbles to prevent oversized payload memory spikes before rejection. This ships in the next npm release. Thanks @tdjackey for reporting.
- Media/Understanding: preserve `application/pdf` MIME classification during text-like file heuristics so PDF uploads use PDF extraction paths instead of being inlined as raw text. (#23191) Thanks @claudeplay2026-byte.
- Security/Control UI: block symlink-based out-of-root static file reads by enforcing realpath containment and file-identity checks when serving Control UI assets and SPA fallback `index.html`. This ships in the next npm release. Thanks @tdjackey for reporting.
- Security/Gateway avatars: block symlink traversal during local avatar `data:` URL resolution by enforcing realpath containment and file-identity checks before reads. This ships in the next npm release. Thanks @aether-ai-agent for reporting.
- Security/Control UI: centralize avatar URL/path validation across gateway/config helpers and enforce a 2 MB max size for local agent avatar files before `/avatar` resolution, reducing oversized-avatar memory risk without changing supported avatar formats.
- Security/MSTeams media: enforce allowlist checks for SharePoint reference attachment URLs and redirect targets during Graph-backed media fetches so redirect chains cannot escape configured media host boundaries. This ships in the next npm release. Thanks @tdjackey for reporting.
- Security/macOS discovery: fail closed for unresolved discovery endpoints by clearing stale remote selection values, use resolved service host only for SSH target derivation, and keep remote URL config aligned with resolved endpoint availability. (#21618) Thanks @bmendonca3.
- Chat/Usage/TUI: strip synthetic inbound metadata blocks (including `Conversation info` and trailing `Untrusted context` channel metadata wrappers) from displayed conversation history so internal prompt context no longer leaks into user-visible logs.
- CI/Tests: fix TypeScript case-table typing and lint assertion regressions so `pnpm check` passes again after Synology Chat landing. (#23012) Thanks @druide67.
- Security/Browser relay: harden extension relay auth token handling for `/extension` and `/cdp` pathways.
- Cron: persist `delivered` state in cron job records so delivery failures remain visible in status and logs. (#19174) Thanks @simonemacario.
- Config/Doctor: only repair the OAuth credentials directory when affected channels are configured, avoiding fresh-install noise.
- Config/Channels: whitelist `channels.modelByChannel` in config validation and exclude it from plugin auto-enable channel detection so model overrides no longer trigger `unknown channel id` validation errors or bogus `modelByChannel` plugin enables. (#23412) Thanks @ProspectOre.
- Config/Bindings: allow optional `bindings[].comment` in strict config validation so annotated binding entries no longer fail load. (#23458) Thanks @echoVic.
- Usage/Pricing: correct MiniMax M2.5 pricing defaults to fix inflated cost reporting. (#22755) Thanks @miloudbelarebia.
- Gateway/Daemon: verify gateway health after daemon restart.
- Agents/UI text: stop rewriting normal assistant billing/payment language outside explicit error contexts. (#17834) Thanks @niceysam.

## 2026.2.21

### Changes

- Models/Google: add Gemini 3.1 support (`google/gemini-3.1-pro-preview`).
- Providers/Onboarding: add Volcano Engine (Doubao) and BytePlus providers/models (including coding variants), wire onboarding auth choices for interactive + non-interactive flows, and align docs to `volcengine-api-key`. (#7967) Thanks @funmore123.
- Channels/CLI: add per-account/channel `defaultTo` outbound routing fallback so `openclaw agent --deliver` can send without explicit `--reply-to` when a default target is configured. (#16985) Thanks @KirillShchetinin.
- Channels: allow per-channel model overrides via `channels.modelByChannel` and note them in /status. Thanks @thewilloftheshadow.
- Telegram/Streaming: simplify preview streaming config to `channels.telegram.streaming` (boolean), auto-map legacy `streamMode` values, and remove block-vs-partial preview branching. (#22012) thanks @obviyus.
- Discord/Streaming: add stream preview mode for live draft replies with partial/block options and configurable chunking. Thanks @thewilloftheshadow. Inspiration @neoagentic-ship-it.
- Discord/Telegram: add configurable lifecycle status reactions for queued/thinking/tool/done/error phases with a shared controller and emoji/timing overrides. Thanks @wolly-tundracube and @thewilloftheshadow.
- Discord/Voice: add voice channel join/leave/status via `/vc`, plus auto-join configuration for realtime voice conversations. Thanks @thewilloftheshadow.
- Discord: add configurable ephemeral defaults for slash-command responses. (#16563) Thanks @wei.
- Discord: support updating forum `available_tags` via channel edit actions for forum tag management. (#12070) Thanks @xiaoyaner0201.
- Discord: include channel topics in trusted inbound metadata on new sessions. Thanks @thewilloftheshadow.
- Discord/Subagents: add thread-bound subagent sessions on Discord with per-thread focus/list controls and thread-bound continuation routing for spawned helper agents. (#21805) Thanks @onutc.
- iOS/Chat: clean chat UI noise by stripping inbound untrusted metadata/timestamp prefixes, formatting tool outputs into concise summaries/errors, compacting the composer while typing, and supporting tap-to-dismiss keyboard in chat view. (#22122) thanks @mbelinky.
- iOS/Watch: bridge mirrored watch prompt notification actions into iOS quick-reply handling, including queued action handoff until app model initialization. (#22123) thanks @mbelinky.
- iOS/Gateway: stabilize background wake and reconnect behavior with background reconnect suppression/lease windows, BGAppRefresh wake fallback, location wake hook throttling, and APNs wake retry+nudge instrumentation. (#21226) thanks @mbelinky.
- Auto-reply/UI: add model fallback lifecycle visibility in verbose logs, /status active-model context with fallback reason, and cohesive WebUI fallback indicators. (#20704) Thanks @joshavant.
- MSTeams: dedupe sent-message cache storage by removing duplicate per-message Set storage and using timestamps Map keys as the single membership source. (#22514) Thanks @TaKO8Ki.
- Agents/Subagents: default subagent spawn depth now uses shared `maxSpawnDepth=2`, enabling depth-1 orchestrator spawning by default while keeping depth policy checks consistent across spawn and prompt paths. (#22223) Thanks @tyler6204.
- Security/Agents: make owner-ID obfuscation use a dedicated HMAC secret from configuration (`ownerDisplaySecret`) and update hashing behavior so obfuscation is decoupled from gateway token handling for improved control. (#7343) Thanks @vincentkoc.
- Security/Infra: switch gateway lock and tool-call synthetic IDs from SHA-1 to SHA-256 with unchanged truncation length to strengthen hash basis while keeping deterministic behavior and lock key format. (#7343) Thanks @vincentkoc.
- Dependencies/Tooling: add non-blocking dead-code scans in CI via Knip/ts-prune/ts-unused-exports to surface unused dependencies and exports earlier. (#22468) Thanks @vincentkoc.
- Dependencies/Unused Dependencies: remove or scope unused root and extension deps (`@larksuiteoapi/node-sdk`, `signal-utils`, `ollama`, `lit`, `@lit/context`, `@lit-labs/signals`, `@microsoft/agents-hosting-express`, `@microsoft/agents-hosting-extensions-teams`, and plugin-local `openclaw` devDeps in `extensions/open-prose`, `extensions/lobster`, and `extensions/llm-task`). (#22471, #22495) Thanks @vincentkoc.
- Dependencies/A2UI: harden dependency resolution after root cleanup (resolve `lit`, `@lit/context`, `@lit-labs/signals`, and `signal-utils` from workspace/root) and simplify bundling fallback behavior, including `pnpm dlx rolldown` compatibility. (#22481, #22507) Thanks @vincentkoc.

### Fixes

- Agents/Bootstrap: skip malformed bootstrap files with missing/invalid paths instead of crashing agent sessions; hooks using `filePath` (or non-string `path`) are skipped with a warning. (#22693, #22698) Thanks @arosstale.
- Security/Agents: cap embedded Pi runner outer retry loop with a higher profile-aware dynamic limit (32-160 attempts) and return an explicit `retry_limit` error payload when retries never converge, preventing unbounded internal retry cycles (`GHSA-76m6-pj3w-v7mf`).
- Telegram: detect duplicate bot-token ownership across Telegram accounts at startup/status time, mark secondary accounts as not configured with an explicit fix message, and block duplicate account startup before polling to avoid endless `getUpdates` conflict loops.
- Agents/Tool images: include source filenames in `agents/tool-images` resize logs so compression events can be traced back to specific files.
- Providers/OAuth: harden Qwen and Chutes refresh handling by validating refresh response expiry values and preserving prior refresh tokens when providers return empty refresh token fields, with regression coverage for empty-token responses.
- Models/Kimi-Coding: add missing implicit provider template for `kimi-coding` with correct `anthropic-messages` API type and base URL, fixing 403 errors when using Kimi for Coding. (#22409)
- Auto-reply/Tools: forward `senderIsOwner` through embedded queued/followup runner params so owner-only tools remain available for authorized senders. (#22296) thanks @hcoj.
- Discord: restore model picker back navigation when a provider is missing and document the Discord picker flow. (#21458) Thanks @pejmanjohn and @thewilloftheshadow.
- Memory/QMD: respect per-agent `memorySearch.enabled=false` during gateway QMD startup initialization, split multi-collection QMD searches into per-collection queries (`search`/`vsearch`/`query`) to avoid sparse-term drops, prefer collection-hinted doc resolution to avoid stale-hash collisions, retry boot updates on transient lock/timeout failures, skip `qmd embed` in BM25-only `search` mode (including `memory index --force`), and serialize embed runs globally with failure backoff to prevent CPU storms on multi-agent hosts. (#20581, #21590, #20513, #20001, #21266, #21583, #20346, #19493) Thanks @danielrevivo, @zanderkrause, @sunyan034-cmd, @tilleulenspiegel, @dae-oss, @adamlongcreativellc, @jonathanadams96, and @kiliansitel.
- Memory/Builtin: prevent automatic sync races with manager shutdown by skipping post-close sync starts and waiting for in-flight sync before closing SQLite, so `onSearch`/`onSessionStart` no longer fail with `database is not open` in ephemeral CLI flows. (#20556, #7464) Thanks @FuzzyTG and @henrybottter.
- Providers/Copilot: drop persisted assistant `thinking` blocks for Claude models (while preserving turn structure/tool blocks) so follow-up requests no longer fail on invalid `thinkingSignature` payloads. (#19459) Thanks @jackheuberger.
- Providers/Copilot: add `claude-sonnet-4.6` and `claude-sonnet-4.5` to the default GitHub Copilot model catalog and add coverage for model-list/definition helpers. (#20270, fixes #20091) Thanks @Clawborn.
- Auto-reply/WebChat: avoid defaulting inbound runtime channel labels to unrelated providers (for example `whatsapp`) for webchat sessions so channel-specific formatting guidance stays accurate. (#21534) Thanks @lbo728.
- Status: include persisted `cacheRead`/`cacheWrite` in session summaries so compact `/status` output consistently shows cache hit percentages from real session data.
- Sessions/Usage: persist `totalTokens` from `promptTokens` snapshots even when providers omit structured usage payloads, so session history/status no longer regress to `unknown` token utilization for otherwise successful runs. (#21819) Thanks @zymclaw.
- Heartbeat/Cron: restore interval heartbeat behavior so missing `HEARTBEAT.md` no longer suppresses runs (only effectively empty files skip), preserving prompt-driven and tagged-cron execution paths.
- WhatsApp/Cron/Heartbeat: enforce allowlisted routing for implicit scheduled/system delivery by merging pairing-store + configured `allowFrom` recipients, selecting authorized recipients when last-route context points to a non-allowlisted chat, and preventing heartbeat fan-out to recent unauthorized chats.
- Heartbeat/Active hours: constrain active-hours `24` sentinel parsing to `24:00` in time validation so invalid values like `24:30` are rejected early. (#21410) thanks @adhitShet.
- Heartbeat: treat `activeHours` windows with identical `start`/`end` times as zero-width (always outside the window) instead of always-active. (#21408) thanks @adhitShet.
- CLI/Pairing: default `pairing list` and `pairing approve` to the sole available pairing channel when omitted, so TUI-only setups can recover from `pairing required` without guessing channel arguments. (#21527) Thanks @losts1.
- TUI/Pairing: show explicit pairing-required recovery guidance after gateway disconnects that return `pairing required`, including approval steps to unblock quickstart TUI hatching on fresh installs. (#21841) Thanks @nicolinux.
- TUI/Input: suppress duplicate backspace events arriving in the same input burst window so SSH sessions no longer delete two characters per backspace press in the composer. (#19318) Thanks @eheimer.
- TUI/Models: scope `models.list` to the configured model allowlist (`agents.defaults.models`) so `/model` picker no longer floods with unrelated catalog entries by default. (#18816) Thanks @fwends.
- TUI/Heartbeat: suppress heartbeat ACK/prompt noise in chat streaming when `showOk` is disabled, while still preserving non-ACK heartbeat alerts in final output. (#20228) Thanks @bhalliburton.
- TUI/History: cap chat-log component growth and prune stale render nodes/references so large default history loads no longer overflow render recursion with `RangeError: Maximum call stack size exceeded`. (#18068) Thanks @JaniJegoroff.
- Memory/QMD: diversify mixed-source search ranking when both session and memory collections are present so session transcript hits no longer crowd out durable memory-file matches in top results. (#19913) Thanks @alextempr.
- Memory/Tools: return explicit `unavailable` warnings/actions from `memory_search` when embedding/provider failures occur (including quota exhaustion), so disabled memory does not look like an empty recall result. (#21894) Thanks @XBS9.
- Session/Startup: require the `/new` and `/reset` greeting path to run Session Startup file-reading instructions before responding, so daily memory startup context is not skipped on fresh-session greetings. (#22338) Thanks @armstrong-pv.
- Auth/Onboarding: align OAuth profile-id config mapping with stored credential IDs for OpenAI Codex and Chutes flows, preventing `provider:default` mismatches when OAuth returns email-scoped credentials. (#12692) thanks @mudrii.
- Provider/HTTP: treat HTTP 503 as failover-eligible for LLM provider errors. (#21086) Thanks @Protocol-zero-0.
- Slack: pass `recipient_team_id` / `recipient_user_id` through Slack native streaming calls so `chat.startStream`/`appendStream`/`stopStream` work reliably across DMs and Slack Connect setups, and disable block streaming when native streaming is active. (#20988) Thanks @Dithilli. Earlier recipient-ID groundwork was contributed in #20377 by @AsserAl1012.
- CLI/Config: add canonical `--strict-json` parsing for `config set` and keep `--json` as a legacy alias to reduce help/behavior drift. (#21332) thanks @adhitShet.
- CLI/Config: preserve explicitly unset config paths in persisted JSON after writes so `openclaw config unset <path>` no longer re-introduces defaulted keys (for example `commands.ownerDisplay`) through schema normalization. (#22984) Thanks @aronchick.
- CLI: keep `openclaw -v` as a root-only version alias so subcommand `-v, --verbose` flags (for example ACP/hooks/skills) are no longer intercepted globally. (#21303) thanks @adhitShet.
- Memory: return empty snippets when `memory_get`/QMD read files that have not been created yet, and harden memory indexing/session helpers against ENOENT races so missing Markdown no longer crashes tools. (#20680) Thanks @pahdo.
- Telegram/Streaming: always clean up draft previews even when dispatch throws before fallback handling, preventing orphaned preview messages during failed runs. (#19041) thanks @mudrii.
- Telegram/Streaming: split reasoning and answer draft preview lanes to prevent cross-lane overwrites, and ignore literal `<think>` tags inside inline/fenced code snippets so sample markup is not misrouted as reasoning. (#20774) Thanks @obviyus.
- Telegram/Streaming: restore 30-char first-preview debounce and scope `NO_REPLY` prefix suppression to partial sentinel fragments so normal `No...` text is not filtered. (#22613) thanks @obviyus.
- Telegram/Status reactions: refresh stall timers on repeated phase updates and honor ack-reaction scope when lifecycle reactions are enabled, preventing false stall emojis and unwanted group reactions. Thanks @wolly-tundracube and @thewilloftheshadow.
- Telegram/Status reactions: keep lifecycle reactions active when available-reactions lookup fails by falling back to unrestricted variant selection instead of suppressing reaction updates. (#22380) thanks @obviyus.
- Discord/Events: await `DiscordMessageListener` message handlers so regular `MESSAGE_CREATE` traffic is processed through queue ordering/timeout flow instead of fire-and-forget drops. (#22396) Thanks @sIlENtbuffER.
- Discord/Streaming: apply `replyToMode: first` only to the first Discord chunk so block-streamed replies do not spam mention pings. (#20726) Thanks @thewilloftheshadow for the report.
- Discord/Components: map DM channel targets back to user-scoped component sessions so button/select interactions stay in the main DM session. Thanks @thewilloftheshadow.
- Discord/Allowlist: lazy-load guild lists when resolving Discord user allowlists so ID-only entries resolve even if guild fetch fails. (#20208) Thanks @zhangjunmengyang.
- Discord/Gateway: handle close code 4014 (missing privileged gateway intents) without crashing the gateway. Thanks @thewilloftheshadow.
- Discord: ingest inbound stickers as media so sticker-only messages and forwarded stickers are visible to agents. Thanks @thewilloftheshadow.
- Auto-reply/Runner: emit `onAgentRunStart` only after agent lifecycle or tool activity begins (and only once per run), so fallback preflight errors no longer mark runs as started. (#21165) Thanks @shakkernerd.
- Auto-reply/Tool results: serialize tool-result delivery and keep the delivery chain progressing after individual failures so concurrent tool outputs preserve user-visible ordering. (#21231) thanks @ahdernasr.
- Auto-reply/Prompt caching: restore prefix-cache stability by keeping inbound system metadata session-stable and moving per-message IDs (`message_id`, `message_id_full`, `reply_to_id`, `sender_id`) into untrusted conversation context. (#20597) Thanks @anisoptera.
- iOS/Watch: add actionable watch approval/reject controls and quick-reply actions so watch-originated approvals and responses can be sent directly from notification flows. (#21996) Thanks @mbelinky.
- iOS/Watch: refresh iOS and watch app icon assets with the lobster icon set to keep phone/watch branding aligned. (#21997) Thanks @mbelinky.


**Investigating modelByChannel schema**

I'm focused on finding the exact syntax for `channels.modelByChannel` in OpenClaw 2026.2.21-2 config validation, since earlier attempts caused gateway crashes due to format errors. I'll try pulling the configuration reference docs via curl and filter for `modelByChannel` to locate the relevant schema details, staying cautious to avoid guessing without confirmation.

curl -sL https://docs.openclaw.ai/gateway/configuration-reference | grep -n "modelByChannel"