Skip to content

fix: skip Telegram command sync when menu is unchanged#32059

Merged
steipete merged 2 commits intoopenclaw:mainfrom
scoootscooob:fix/telegram-command-sync-throttle
Mar 2, 2026
Merged

fix: skip Telegram command sync when menu is unchanged#32059
steipete merged 2 commits intoopenclaw:mainfrom
scoootscooob:fix/telegram-command-sync-throttle

Conversation

@scoootscooob
Copy link
Contributor

Closes #32017

Summary

  • Hash the command list (SHA-256, truncated to 16 hex chars) and cache it to <stateDir>/telegram/command-hash-<accountId>.txt
  • On restart, compare current hash against cached — skip the deleteMyCommands + setMyCommands round-trip when nothing changed
  • Prevents Telegram 429 rate-limit errors when the gateway restarts several times in quick succession

Changes

  • bot-native-command-menu.ts: added hashCommandList(), file-based hash caching (readCachedCommandHash / writeCachedCommandHash), and early-return skip logic in syncTelegramMenuCommands
  • bot-native-commands.ts: pass accountId through to syncTelegramMenuCommands
  • bot-native-command-menu.test.ts: 4 new tests — hash stability, hash sensitivity, skip-when-unchanged, plus existing retry test still passes

Test plan

  • hashCommandList produces stable hashes regardless of input order
  • hashCommandList produces different hashes for different command lists
  • Second sync with same commands skips setMyCommands and logs skip message
  • Existing retry-on-BOT_COMMANDS_TOO_MUCH test still passes
  • npx tsgo type check passes

🤖 Generated with Claude Code

@openclaw-barnacle openclaw-barnacle bot added channel: telegram Channel integration: telegram size: S labels Mar 2, 2026
Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 011cd4bc39

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment on lines +113 to +116
function resolveCommandHashPath(accountId?: string): string {
const stateDir = resolveStateDir(process.env, os.homedir);
const normalized = accountId?.trim().replace(/[^a-z0-9._-]+/gi, "_") || "default";
return path.join(stateDir, "telegram", `command-hash-${normalized}.txt`);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Include bot identity in command-hash cache key

The new cache file is keyed only by accountId, so if channels.telegram.accounts.<id>.token is rotated (or the same account id is pointed at a different bot), cachedHash === currentHash and syncTelegramMenuCommands exits before calling setMyCommands. That leaves the new bot without a refreshed command menu, whereas pre-change startup always re-synced. The cache key (or cached payload) needs to include bot identity to avoid cross-bot false positives.

Useful? React with 👍 / 👎.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 2, 2026

Greptile Summary

This PR adds SHA-256-based command-list hashing and file-based caching to avoid redundant deleteMyCommands/setMyCommands round-trips on gateway restarts, preventing Telegram 429 rate-limit errors. The implementation is clean and the new tests are well-isolated.

Key issues found:

  • The pre-existing test "deletes stale commands before setting new menu" was not updated to use a unique accountId. Because it falls back to the shared "default" hash-file path, the first run writes the hash to disk and every subsequent run skips the sync entirely — causing setMyCommands to never be called and the test to hang/fail. All new tests correctly pass a Date.now()-based accountId, but this existing test was overlooked.
  • writeCachedCommandHash accepts hash as string | undefined when all real call sites pass a guaranteed string; making it required would provide better type safety.

Confidence Score: 3/5

  • Safe to merge once the pre-existing test is fixed to use a unique accountId; the production logic is correct.
  • The implementation itself is solid and solves the rate-limit problem correctly. However, the omission of a unique accountId in the existing "deletes stale commands" test will cause it to fail on every run after the first, which is a concrete breakage that should be fixed before merging.
  • src/telegram/bot-native-command-menu.test.ts — the "deletes stale commands before setting new menu" test needs a unique accountId to avoid being broken by the new hash-caching logic.

Last reviewed commit: 011cd4b

Copy link
Contributor

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

3 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment on lines +127 to +139
async function writeCachedCommandHash(accountId?: string, hash?: string): Promise<void> {
if (!hash) {
return;
}
const filePath = resolveCommandHashPath(accountId);
try {
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, hash, "utf-8");
} catch {
// Best-effort: failing to cache the hash just means the next restart
// will sync commands again, which is the pre-fix behaviour.
}
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

hash parameter type allows undefined unnecessarily

writeCachedCommandHash(accountId?: string, hash?: string) accepts hash as optional and guards against it being falsy, but every call site passes currentHash, which is a string returned by hashCommandList. Making hash required would let TypeScript catch any accidental call site that omits it and removes the implicit skip-when-falsy guard:

Suggested change
async function writeCachedCommandHash(accountId?: string, hash?: string): Promise<void> {
if (!hash) {
return;
}
const filePath = resolveCommandHashPath(accountId);
try {
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, hash, "utf-8");
} catch {
// Best-effort: failing to cache the hash just means the next restart
// will sync commands again, which is the pre-fix behaviour.
}
}
async function writeCachedCommandHash(accountId?: string, hash: string): Promise<void> {
const filePath = resolveCommandHashPath(accountId);
try {
await fs.mkdir(path.dirname(filePath), { recursive: true });
await fs.writeFile(filePath, hash, "utf-8");
} catch {
// Best-effort: failing to cache the hash just means the next restart
// will sync commands again, which is the pre-fix behaviour.
}
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/bot-native-command-menu.ts
Line: 127-139

Comment:
**`hash` parameter type allows `undefined` unnecessarily**

`writeCachedCommandHash(accountId?: string, hash?: string)` accepts `hash` as optional and guards against it being falsy, but every call site passes `currentHash`, which is a `string` returned by `hashCommandList`. Making `hash` required would let TypeScript catch any accidental call site that omits it and removes the implicit skip-when-falsy guard:

```suggestion
async function writeCachedCommandHash(accountId?: string, hash: string): Promise<void> {
  const filePath = resolveCommandHashPath(accountId);
  try {
    await fs.mkdir(path.dirname(filePath), { recursive: true });
    await fs.writeFile(filePath, hash, "utf-8");
  } catch {
    // Best-effort: failing to cache the hash just means the next restart
    // will sync commands again, which is the pre-fix behaviour.
  }
}
```

How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Mar 2, 2026

Additional Comments (1)

src/telegram/bot-native-command-menu.test.ts
Existing test will fail on second and subsequent runs

The "deletes stale commands before setting new menu" test does not provide an accountId, so it resolves to the "default" path (<stateDir>/telegram/command-hash-default.txt). The first time this test runs, the hash for [{ command: "cmd", description: "Command" }] is written to disk. On the next run — including the very next vitest invocation locally — readCachedCommandHash will return the cached hash, it will match currentHash, and syncTelegramMenuCommands will skip the entire sync without calling deleteMyCommands or setMyCommands. The vi.waitFor assertion on setMyCommands will then time out and the test will fail.

Fix: pass a unique accountId (e.g. test-delete-${Date.now()}) just like the new "skips sync when command hash is unchanged" test does, so the cached hash from one run never interferes with another:

  it("deletes stale commands before setting new menu", async () => {
    const callOrder: string[] = [];
    const deleteMyCommands = vi.fn(async () => {
      callOrder.push("delete");
    });
    const setMyCommands = vi.fn(async () => {
      callOrder.push("set");
    });

    syncTelegramMenuCommands({
      bot: {
        api: {
          deleteMyCommands,
          setMyCommands,
        },
      } as unknown as Parameters<typeof syncTelegramMenuCommands>[0]["bot"],
      runtime: {} as Parameters<typeof syncTelegramMenuCommands>[0]["runtime"],
      commandsToRegister: [{ command: "cmd", description: "Command" }],
      accountId: `test-delete-stale-${Date.now()}`,
    });

    await vi.waitFor(() => {
      expect(setMyCommands).toHaveBeenCalled();
    });

    expect(callOrder).toEqual(["delete", "set"]);
  });
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/telegram/bot-native-command-menu.test.ts
Line: 64-89

Comment:
**Existing test will fail on second and subsequent runs**

The "deletes stale commands before setting new menu" test does not provide an `accountId`, so it resolves to the `"default"` path (`<stateDir>/telegram/command-hash-default.txt`). The first time this test runs, the hash for `[{ command: "cmd", description: "Command" }]` is written to disk. On the next run — including the very next `vitest` invocation locally — `readCachedCommandHash` will return the cached hash, it will match `currentHash`, and `syncTelegramMenuCommands` will skip the entire sync without calling `deleteMyCommands` or `setMyCommands`. The `vi.waitFor` assertion on `setMyCommands` will then time out and the test will fail.

Fix: pass a unique `accountId` (e.g. `test-delete-${Date.now()}`) just like the new "skips sync when command hash is unchanged" test does, so the cached hash from one run never interferes with another:

```suggestion
  it("deletes stale commands before setting new menu", async () => {
    const callOrder: string[] = [];
    const deleteMyCommands = vi.fn(async () => {
      callOrder.push("delete");
    });
    const setMyCommands = vi.fn(async () => {
      callOrder.push("set");
    });

    syncTelegramMenuCommands({
      bot: {
        api: {
          deleteMyCommands,
          setMyCommands,
        },
      } as unknown as Parameters<typeof syncTelegramMenuCommands>[0]["bot"],
      runtime: {} as Parameters<typeof syncTelegramMenuCommands>[0]["runtime"],
      commandsToRegister: [{ command: "cmd", description: "Command" }],
      accountId: `test-delete-stale-${Date.now()}`,
    });

    await vi.waitFor(() => {
      expect(setMyCommands).toHaveBeenCalled();
    });

    expect(callOrder).toEqual(["delete", "set"]);
  });
```

How can I resolve this? If you propose a fix, please make it concise.

scoootscooob and others added 2 commits March 2, 2026 19:22
Hash the command list and cache it to disk per account. On restart,
compare the current hash against the cached one and skip the
deleteMyCommands + setMyCommands round-trip when nothing changed.
This prevents 429 rate-limit errors when the gateway restarts
several times in quick succession.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@steipete steipete force-pushed the fix/telegram-command-sync-throttle branch from 011cd4b to 0872ba2 Compare March 2, 2026 19:25
@steipete steipete merged commit d486b0a into openclaw:main Mar 2, 2026
10 checks passed
@steipete
Copy link
Contributor

steipete commented Mar 2, 2026

Landed with review fixes.

  • Gate: pnpm -s vitest run src/telegram/bot-native-command-menu.test.ts
  • Land commit: LAND_SHA_PLACEHOLDER
  • Merge commit: MERGE_SHA_PLACEHOLDER

Thanks @scoootscooob!

@steipete
Copy link
Contributor

steipete commented Mar 2, 2026

Correction with concrete SHAs:

Copy link

@chatgpt-codex-connector chatgpt-codex-connector bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 0872ba2499

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines 185 to 187
if (commandsToRegister.length === 0) {
await writeCachedCommandHash(accountId, botIdentity, currentHash);
return;

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Avoid caching empty-menu hash after failed delete

When commandsToRegister is empty, this branch writes the new hash even if deleteMyCommands failed just above (that failure is swallowed with .catch(() => {})). In that failure scenario (e.g., transient Telegram/API outage while disabling commands), the stale menu remains on Telegram, but future startups will see the cached hash and skip sync entirely, so the cleanup is never retried. Before this change, restarts would keep retrying the delete path.

Useful? React with 👍 / 👎.

steipete added a commit to liuxiaopai-ai/openclaw that referenced this pull request Mar 2, 2026
steipete added a commit that referenced this pull request Mar 2, 2026
* fix(plugins): fallback install entrypoints for legacy manifests

* Voice Call: enforce exact webhook path match

* Tests: isolate webhook path suite and reset cron auth state

* chore: keep #31930 scoped to voice webhook path fix

* fix: add changelog for exact voice webhook path match (#31930) (thanks @afurm)

* fix: handle HTTP 529 (Anthropic overloaded) in failover error classification

Classify Anthropic's 529 status code as "rate_limit" so model fallback
triggers reliably without depending on fragile message-based detection.

Closes #28502

* fix: add changelog for HTTP 529 failover classification (#31854) (thanks @bugkill3r)

* fix(slack): guard against undefined text in includes calls during mention handling

* fix: add changelog for mentions/slack null-safe guards (#31865) (thanks @stone-jin)

* fix(memory-lancedb): pass dimensions to embedding API call

- Add dimensions parameter to Embeddings constructor
- Pass dimensions to OpenAI embeddings.create() API call
- Fixes dimension mismatch when using custom embedding models like DashScope text-embedding-v4

* fix: add regression for memory-lancedb dimensions pass-through (#32036) (thanks @scotthuang)

* fix(telegram): guard malformed native menu specs

* fix: harden plugin command registration + telegram menu guard (#31997) (thanks @liuxiaopai-ai)

* fix(gateway): restart heartbeat on model config changes

* fix: add changelog credit for heartbeat model reload (#32046) (thanks @stakeswky)

* test(process): replace no-output timer subprocess with spawn mock

* test(perf): trim repeated setup in cron memory and config suites

* test(perf): reduce per-case setup in script and git-hook tests

* fix(slack): scope debounce key by message timestamp to prevent cross-thread collisions

Top-level channel messages from the same sender shared a bare channel
debounce key, causing concurrent messages in different threads to merge
into a single reply on the wrong thread. Now the debounce key includes
the message timestamp for top-level messages, matching how the downstream
session layer already scopes by canonicalThreadId.

Extracted buildSlackDebounceKey() for testability.

Closes #31935

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: harden slack debounce key routing and ordering (#31951) (thanks @scoootscooob)

* fix(openrouter): skip reasoning.effort injection for x-ai/grok models

x-ai/grok models on OpenRouter do not support the reasoning.effort
parameter and reject payloads containing it with "Invalid arguments
passed to the model." Skip reasoning injection for these models, the
same way we already skip it for the dynamic "auto" routing model.

Closes #32039

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add changelog credit for openrouter x-ai reasoning guard (#32054) (thanks @scoootscooob)

* fix(agents): scope volcengine-plan/byteplus-plan auth lookup to profile resolution

The configure flow stores auth credentials under `provider: "volcengine"`,
but the coding model uses `volcengine-plan` as its provider. Add a scoped
`normalizeProviderIdForAuth` function used only by `listProfilesForProvider`
so coding-plan variants resolve to their base provider for auth credential
lookup without affecting global provider routing.

Closes #31731

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(tools): honor fsPolicy.workspaceOnly in image/pdf tool localRoots

PR #28822 fixed the Write/Edit tools to respect `tools.fs.workspaceOnly`,
but the image and PDF tools still unconditionally include default local
roots (`~/.openclaw/media`, `~/.openclaw/agents`, etc.) when computing
the `localRoots` allowlist for non-sandbox mode.

When `fsPolicy.workspaceOnly` is true, restrict `localRoots` to only the
workspace directory so that files outside the workspace are rejected by
`assertLocalMediaAllowed()`.

Relates to #31716

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add changelog credit for fsPolicy image/pdf propagation (#31882) (thanks @justinhuangcode)

* fix: skip Telegram command sync when menu is unchanged (#32017)

Hash the command list and cache it to disk per account. On restart,
compare the current hash against the cached one and skip the
deleteMyCommands + setMyCommands round-trip when nothing changed.
This prevents 429 rate-limit errors when the gateway restarts
several times in quick succession.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(telegram): scope command-sync hash cache by bot identity (#32059)

* fix: normalize coding-plan providers in auth order validation

* feat(security): Harden Docker browser container chromium flags (#23889) (#31504)

* Gateway: honor OPENCLAW_GATEWAY_URL override for remote/local calls

* Agents: fix sandbox sessionKey usage for PI embedded subagent calls

* Sandbox: tighten browser container Chromium runtime flags

* fix: add sandbox browser defaults for container hardening

* docs: expand sandbox browser default flags list

* fix: make sandbox browser flags optional and preserve gateway env auth overrides

* docs: scope PR 31504 changelog entry

* style: format gateway call override handling

* fix: dedupe sandbox browser chrome args

* fix: preserve remote tls fingerprint for env gateway override

* fix: enforce auth for env gateway URL override

* chore: document gateway override auth security expectations

* fix(delivery): strip HTML tags for plain-text messaging surfaces

Models occasionally produce HTML tags in their output. While these render
fine on web surfaces, they appear as literal text on WhatsApp, Signal,
SMS, IRC, and Telegram.

Add sanitizeForPlainText() utility that converts common inline HTML to
lightweight-markup equivalents and strips remaining tags. Applied in the
outbound delivery pipeline for non-HTML surfaces only.

Closes #31884
See also: #18558

* fix(outbound): harden plain-text HTML sanitization paths (#32034)

* fix(security): harden file installs and race-path tests

* matrix: bootstrap crypto runtime when npm scripts are skipped

* fix(matrix): keep plugin register sync while bootstrapping crypto runtime (#31989)

* perf(runtime): reduce cron persistence and logger overhead

* test(perf): use prebuilt plugin install archive fixtures

* test(perf): increase guardrail scan read concurrency

* fix(queue): restart drain when message enqueued after idle window

After a drain loop empties the queue it deletes the key from
FOLLOWUP_QUEUES.  If a new message arrives at that moment
enqueueFollowupRun creates a fresh queue object with draining:false
but never starts a drain, leaving the message stranded until the
next run completes and calls finalizeWithFollowup.

Fix: persist the most recent runFollowup callback per queue key in
FOLLOWUP_RUN_CALLBACKS (drain.ts).  enqueueFollowupRun now calls
kickFollowupDrainIfIdle after a successful push; if a cached
callback exists and no drain is running it calls scheduleFollowupDrain
to restart immediately.  clearSessionQueues cleans up the callback
cache alongside the queue state.

* fix: avoid stale followup drain callbacks (#31902) (thanks @Lanfei)

* fix(synology-chat): read cfg from outbound context so incomingUrl resolves

* fix: require openclaw.extensions for plugin installs (#32055) (thanks @liuxiaopai-ai)

---------

Co-authored-by: Andrii Furmanets <furmanets.andriy@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Saurabh <skmishra1991@gmail.com>
Co-authored-by: stone-jin <1520006273@qq.com>
Co-authored-by: scotthuang <scotthuang@tencent.com>
Co-authored-by: User <user@example.com>
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: justinhuangcode <justinhuangcode@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: AytuncYildizli <cryptosquanch@gmail.com>
Co-authored-by: bmendonca3 <bmendonca3@users.noreply.github.com>
Co-authored-by: Jealous <CooLanfei@163.com>
Co-authored-by: white-rm <zhang.xujin@xydigit.com>
execute008 pushed a commit to execute008/openclaw that referenced this pull request Mar 2, 2026
execute008 pushed a commit to execute008/openclaw that referenced this pull request Mar 2, 2026
* fix(plugins): fallback install entrypoints for legacy manifests

* Voice Call: enforce exact webhook path match

* Tests: isolate webhook path suite and reset cron auth state

* chore: keep openclaw#31930 scoped to voice webhook path fix

* fix: add changelog for exact voice webhook path match (openclaw#31930) (thanks @afurm)

* fix: handle HTTP 529 (Anthropic overloaded) in failover error classification

Classify Anthropic's 529 status code as "rate_limit" so model fallback
triggers reliably without depending on fragile message-based detection.

Closes openclaw#28502

* fix: add changelog for HTTP 529 failover classification (openclaw#31854) (thanks @bugkill3r)

* fix(slack): guard against undefined text in includes calls during mention handling

* fix: add changelog for mentions/slack null-safe guards (openclaw#31865) (thanks @stone-jin)

* fix(memory-lancedb): pass dimensions to embedding API call

- Add dimensions parameter to Embeddings constructor
- Pass dimensions to OpenAI embeddings.create() API call
- Fixes dimension mismatch when using custom embedding models like DashScope text-embedding-v4

* fix: add regression for memory-lancedb dimensions pass-through (openclaw#32036) (thanks @scotthuang)

* fix(telegram): guard malformed native menu specs

* fix: harden plugin command registration + telegram menu guard (openclaw#31997) (thanks @liuxiaopai-ai)

* fix(gateway): restart heartbeat on model config changes

* fix: add changelog credit for heartbeat model reload (openclaw#32046) (thanks @stakeswky)

* test(process): replace no-output timer subprocess with spawn mock

* test(perf): trim repeated setup in cron memory and config suites

* test(perf): reduce per-case setup in script and git-hook tests

* fix(slack): scope debounce key by message timestamp to prevent cross-thread collisions

Top-level channel messages from the same sender shared a bare channel
debounce key, causing concurrent messages in different threads to merge
into a single reply on the wrong thread. Now the debounce key includes
the message timestamp for top-level messages, matching how the downstream
session layer already scopes by canonicalThreadId.

Extracted buildSlackDebounceKey() for testability.

Closes openclaw#31935

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: harden slack debounce key routing and ordering (openclaw#31951) (thanks @scoootscooob)

* fix(openrouter): skip reasoning.effort injection for x-ai/grok models

x-ai/grok models on OpenRouter do not support the reasoning.effort
parameter and reject payloads containing it with "Invalid arguments
passed to the model." Skip reasoning injection for these models, the
same way we already skip it for the dynamic "auto" routing model.

Closes openclaw#32039

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add changelog credit for openrouter x-ai reasoning guard (openclaw#32054) (thanks @scoootscooob)

* fix(agents): scope volcengine-plan/byteplus-plan auth lookup to profile resolution

The configure flow stores auth credentials under `provider: "volcengine"`,
but the coding model uses `volcengine-plan` as its provider. Add a scoped
`normalizeProviderIdForAuth` function used only by `listProfilesForProvider`
so coding-plan variants resolve to their base provider for auth credential
lookup without affecting global provider routing.

Closes openclaw#31731

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(tools): honor fsPolicy.workspaceOnly in image/pdf tool localRoots

PR openclaw#28822 fixed the Write/Edit tools to respect `tools.fs.workspaceOnly`,
but the image and PDF tools still unconditionally include default local
roots (`~/.openclaw/media`, `~/.openclaw/agents`, etc.) when computing
the `localRoots` allowlist for non-sandbox mode.

When `fsPolicy.workspaceOnly` is true, restrict `localRoots` to only the
workspace directory so that files outside the workspace are rejected by
`assertLocalMediaAllowed()`.

Relates to openclaw#31716

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add changelog credit for fsPolicy image/pdf propagation (openclaw#31882) (thanks @justinhuangcode)

* fix: skip Telegram command sync when menu is unchanged (openclaw#32017)

Hash the command list and cache it to disk per account. On restart,
compare the current hash against the cached one and skip the
deleteMyCommands + setMyCommands round-trip when nothing changed.
This prevents 429 rate-limit errors when the gateway restarts
several times in quick succession.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(telegram): scope command-sync hash cache by bot identity (openclaw#32059)

* fix: normalize coding-plan providers in auth order validation

* feat(security): Harden Docker browser container chromium flags (openclaw#23889) (openclaw#31504)

* Gateway: honor OPENCLAW_GATEWAY_URL override for remote/local calls

* Agents: fix sandbox sessionKey usage for PI embedded subagent calls

* Sandbox: tighten browser container Chromium runtime flags

* fix: add sandbox browser defaults for container hardening

* docs: expand sandbox browser default flags list

* fix: make sandbox browser flags optional and preserve gateway env auth overrides

* docs: scope PR 31504 changelog entry

* style: format gateway call override handling

* fix: dedupe sandbox browser chrome args

* fix: preserve remote tls fingerprint for env gateway override

* fix: enforce auth for env gateway URL override

* chore: document gateway override auth security expectations

* fix(delivery): strip HTML tags for plain-text messaging surfaces

Models occasionally produce HTML tags in their output. While these render
fine on web surfaces, they appear as literal text on WhatsApp, Signal,
SMS, IRC, and Telegram.

Add sanitizeForPlainText() utility that converts common inline HTML to
lightweight-markup equivalents and strips remaining tags. Applied in the
outbound delivery pipeline for non-HTML surfaces only.

Closes openclaw#31884
See also: openclaw#18558

* fix(outbound): harden plain-text HTML sanitization paths (openclaw#32034)

* fix(security): harden file installs and race-path tests

* matrix: bootstrap crypto runtime when npm scripts are skipped

* fix(matrix): keep plugin register sync while bootstrapping crypto runtime (openclaw#31989)

* perf(runtime): reduce cron persistence and logger overhead

* test(perf): use prebuilt plugin install archive fixtures

* test(perf): increase guardrail scan read concurrency

* fix(queue): restart drain when message enqueued after idle window

After a drain loop empties the queue it deletes the key from
FOLLOWUP_QUEUES.  If a new message arrives at that moment
enqueueFollowupRun creates a fresh queue object with draining:false
but never starts a drain, leaving the message stranded until the
next run completes and calls finalizeWithFollowup.

Fix: persist the most recent runFollowup callback per queue key in
FOLLOWUP_RUN_CALLBACKS (drain.ts).  enqueueFollowupRun now calls
kickFollowupDrainIfIdle after a successful push; if a cached
callback exists and no drain is running it calls scheduleFollowupDrain
to restart immediately.  clearSessionQueues cleans up the callback
cache alongside the queue state.

* fix: avoid stale followup drain callbacks (openclaw#31902) (thanks @Lanfei)

* fix(synology-chat): read cfg from outbound context so incomingUrl resolves

* fix: require openclaw.extensions for plugin installs (openclaw#32055) (thanks @liuxiaopai-ai)

---------

Co-authored-by: Andrii Furmanets <furmanets.andriy@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Saurabh <skmishra1991@gmail.com>
Co-authored-by: stone-jin <1520006273@qq.com>
Co-authored-by: scotthuang <scotthuang@tencent.com>
Co-authored-by: User <user@example.com>
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: justinhuangcode <justinhuangcode@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: AytuncYildizli <cryptosquanch@gmail.com>
Co-authored-by: bmendonca3 <bmendonca3@users.noreply.github.com>
Co-authored-by: Jealous <CooLanfei@163.com>
Co-authored-by: white-rm <zhang.xujin@xydigit.com>
dawi369 pushed a commit to dawi369/davis that referenced this pull request Mar 3, 2026
dawi369 pushed a commit to dawi369/davis that referenced this pull request Mar 3, 2026
* fix(plugins): fallback install entrypoints for legacy manifests

* Voice Call: enforce exact webhook path match

* Tests: isolate webhook path suite and reset cron auth state

* chore: keep openclaw#31930 scoped to voice webhook path fix

* fix: add changelog for exact voice webhook path match (openclaw#31930) (thanks @afurm)

* fix: handle HTTP 529 (Anthropic overloaded) in failover error classification

Classify Anthropic's 529 status code as "rate_limit" so model fallback
triggers reliably without depending on fragile message-based detection.

Closes openclaw#28502

* fix: add changelog for HTTP 529 failover classification (openclaw#31854) (thanks @bugkill3r)

* fix(slack): guard against undefined text in includes calls during mention handling

* fix: add changelog for mentions/slack null-safe guards (openclaw#31865) (thanks @stone-jin)

* fix(memory-lancedb): pass dimensions to embedding API call

- Add dimensions parameter to Embeddings constructor
- Pass dimensions to OpenAI embeddings.create() API call
- Fixes dimension mismatch when using custom embedding models like DashScope text-embedding-v4

* fix: add regression for memory-lancedb dimensions pass-through (openclaw#32036) (thanks @scotthuang)

* fix(telegram): guard malformed native menu specs

* fix: harden plugin command registration + telegram menu guard (openclaw#31997) (thanks @liuxiaopai-ai)

* fix(gateway): restart heartbeat on model config changes

* fix: add changelog credit for heartbeat model reload (openclaw#32046) (thanks @stakeswky)

* test(process): replace no-output timer subprocess with spawn mock

* test(perf): trim repeated setup in cron memory and config suites

* test(perf): reduce per-case setup in script and git-hook tests

* fix(slack): scope debounce key by message timestamp to prevent cross-thread collisions

Top-level channel messages from the same sender shared a bare channel
debounce key, causing concurrent messages in different threads to merge
into a single reply on the wrong thread. Now the debounce key includes
the message timestamp for top-level messages, matching how the downstream
session layer already scopes by canonicalThreadId.

Extracted buildSlackDebounceKey() for testability.

Closes openclaw#31935

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: harden slack debounce key routing and ordering (openclaw#31951) (thanks @scoootscooob)

* fix(openrouter): skip reasoning.effort injection for x-ai/grok models

x-ai/grok models on OpenRouter do not support the reasoning.effort
parameter and reject payloads containing it with "Invalid arguments
passed to the model." Skip reasoning injection for these models, the
same way we already skip it for the dynamic "auto" routing model.

Closes openclaw#32039

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add changelog credit for openrouter x-ai reasoning guard (openclaw#32054) (thanks @scoootscooob)

* fix(agents): scope volcengine-plan/byteplus-plan auth lookup to profile resolution

The configure flow stores auth credentials under `provider: "volcengine"`,
but the coding model uses `volcengine-plan` as its provider. Add a scoped
`normalizeProviderIdForAuth` function used only by `listProfilesForProvider`
so coding-plan variants resolve to their base provider for auth credential
lookup without affecting global provider routing.

Closes openclaw#31731

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(tools): honor fsPolicy.workspaceOnly in image/pdf tool localRoots

PR openclaw#28822 fixed the Write/Edit tools to respect `tools.fs.workspaceOnly`,
but the image and PDF tools still unconditionally include default local
roots (`~/.openclaw/media`, `~/.openclaw/agents`, etc.) when computing
the `localRoots` allowlist for non-sandbox mode.

When `fsPolicy.workspaceOnly` is true, restrict `localRoots` to only the
workspace directory so that files outside the workspace are rejected by
`assertLocalMediaAllowed()`.

Relates to openclaw#31716

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add changelog credit for fsPolicy image/pdf propagation (openclaw#31882) (thanks @justinhuangcode)

* fix: skip Telegram command sync when menu is unchanged (openclaw#32017)

Hash the command list and cache it to disk per account. On restart,
compare the current hash against the cached one and skip the
deleteMyCommands + setMyCommands round-trip when nothing changed.
This prevents 429 rate-limit errors when the gateway restarts
several times in quick succession.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(telegram): scope command-sync hash cache by bot identity (openclaw#32059)

* fix: normalize coding-plan providers in auth order validation

* feat(security): Harden Docker browser container chromium flags (openclaw#23889) (openclaw#31504)

* Gateway: honor OPENCLAW_GATEWAY_URL override for remote/local calls

* Agents: fix sandbox sessionKey usage for PI embedded subagent calls

* Sandbox: tighten browser container Chromium runtime flags

* fix: add sandbox browser defaults for container hardening

* docs: expand sandbox browser default flags list

* fix: make sandbox browser flags optional and preserve gateway env auth overrides

* docs: scope PR 31504 changelog entry

* style: format gateway call override handling

* fix: dedupe sandbox browser chrome args

* fix: preserve remote tls fingerprint for env gateway override

* fix: enforce auth for env gateway URL override

* chore: document gateway override auth security expectations

* fix(delivery): strip HTML tags for plain-text messaging surfaces

Models occasionally produce HTML tags in their output. While these render
fine on web surfaces, they appear as literal text on WhatsApp, Signal,
SMS, IRC, and Telegram.

Add sanitizeForPlainText() utility that converts common inline HTML to
lightweight-markup equivalents and strips remaining tags. Applied in the
outbound delivery pipeline for non-HTML surfaces only.

Closes openclaw#31884
See also: openclaw#18558

* fix(outbound): harden plain-text HTML sanitization paths (openclaw#32034)

* fix(security): harden file installs and race-path tests

* matrix: bootstrap crypto runtime when npm scripts are skipped

* fix(matrix): keep plugin register sync while bootstrapping crypto runtime (openclaw#31989)

* perf(runtime): reduce cron persistence and logger overhead

* test(perf): use prebuilt plugin install archive fixtures

* test(perf): increase guardrail scan read concurrency

* fix(queue): restart drain when message enqueued after idle window

After a drain loop empties the queue it deletes the key from
FOLLOWUP_QUEUES.  If a new message arrives at that moment
enqueueFollowupRun creates a fresh queue object with draining:false
but never starts a drain, leaving the message stranded until the
next run completes and calls finalizeWithFollowup.

Fix: persist the most recent runFollowup callback per queue key in
FOLLOWUP_RUN_CALLBACKS (drain.ts).  enqueueFollowupRun now calls
kickFollowupDrainIfIdle after a successful push; if a cached
callback exists and no drain is running it calls scheduleFollowupDrain
to restart immediately.  clearSessionQueues cleans up the callback
cache alongside the queue state.

* fix: avoid stale followup drain callbacks (openclaw#31902) (thanks @Lanfei)

* fix(synology-chat): read cfg from outbound context so incomingUrl resolves

* fix: require openclaw.extensions for plugin installs (openclaw#32055) (thanks @liuxiaopai-ai)

---------

Co-authored-by: Andrii Furmanets <furmanets.andriy@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Saurabh <skmishra1991@gmail.com>
Co-authored-by: stone-jin <1520006273@qq.com>
Co-authored-by: scotthuang <scotthuang@tencent.com>
Co-authored-by: User <user@example.com>
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: justinhuangcode <justinhuangcode@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: AytuncYildizli <cryptosquanch@gmail.com>
Co-authored-by: bmendonca3 <bmendonca3@users.noreply.github.com>
Co-authored-by: Jealous <CooLanfei@163.com>
Co-authored-by: white-rm <zhang.xujin@xydigit.com>
OWALabuy pushed a commit to kcinzgg/openclaw that referenced this pull request Mar 4, 2026
OWALabuy pushed a commit to kcinzgg/openclaw that referenced this pull request Mar 4, 2026
* fix(plugins): fallback install entrypoints for legacy manifests

* Voice Call: enforce exact webhook path match

* Tests: isolate webhook path suite and reset cron auth state

* chore: keep openclaw#31930 scoped to voice webhook path fix

* fix: add changelog for exact voice webhook path match (openclaw#31930) (thanks @afurm)

* fix: handle HTTP 529 (Anthropic overloaded) in failover error classification

Classify Anthropic's 529 status code as "rate_limit" so model fallback
triggers reliably without depending on fragile message-based detection.

Closes openclaw#28502

* fix: add changelog for HTTP 529 failover classification (openclaw#31854) (thanks @bugkill3r)

* fix(slack): guard against undefined text in includes calls during mention handling

* fix: add changelog for mentions/slack null-safe guards (openclaw#31865) (thanks @stone-jin)

* fix(memory-lancedb): pass dimensions to embedding API call

- Add dimensions parameter to Embeddings constructor
- Pass dimensions to OpenAI embeddings.create() API call
- Fixes dimension mismatch when using custom embedding models like DashScope text-embedding-v4

* fix: add regression for memory-lancedb dimensions pass-through (openclaw#32036) (thanks @scotthuang)

* fix(telegram): guard malformed native menu specs

* fix: harden plugin command registration + telegram menu guard (openclaw#31997) (thanks @liuxiaopai-ai)

* fix(gateway): restart heartbeat on model config changes

* fix: add changelog credit for heartbeat model reload (openclaw#32046) (thanks @stakeswky)

* test(process): replace no-output timer subprocess with spawn mock

* test(perf): trim repeated setup in cron memory and config suites

* test(perf): reduce per-case setup in script and git-hook tests

* fix(slack): scope debounce key by message timestamp to prevent cross-thread collisions

Top-level channel messages from the same sender shared a bare channel
debounce key, causing concurrent messages in different threads to merge
into a single reply on the wrong thread. Now the debounce key includes
the message timestamp for top-level messages, matching how the downstream
session layer already scopes by canonicalThreadId.

Extracted buildSlackDebounceKey() for testability.

Closes openclaw#31935

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: harden slack debounce key routing and ordering (openclaw#31951) (thanks @scoootscooob)

* fix(openrouter): skip reasoning.effort injection for x-ai/grok models

x-ai/grok models on OpenRouter do not support the reasoning.effort
parameter and reject payloads containing it with "Invalid arguments
passed to the model." Skip reasoning injection for these models, the
same way we already skip it for the dynamic "auto" routing model.

Closes openclaw#32039

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add changelog credit for openrouter x-ai reasoning guard (openclaw#32054) (thanks @scoootscooob)

* fix(agents): scope volcengine-plan/byteplus-plan auth lookup to profile resolution

The configure flow stores auth credentials under `provider: "volcengine"`,
but the coding model uses `volcengine-plan` as its provider. Add a scoped
`normalizeProviderIdForAuth` function used only by `listProfilesForProvider`
so coding-plan variants resolve to their base provider for auth credential
lookup without affecting global provider routing.

Closes openclaw#31731

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(tools): honor fsPolicy.workspaceOnly in image/pdf tool localRoots

PR openclaw#28822 fixed the Write/Edit tools to respect `tools.fs.workspaceOnly`,
but the image and PDF tools still unconditionally include default local
roots (`~/.openclaw/media`, `~/.openclaw/agents`, etc.) when computing
the `localRoots` allowlist for non-sandbox mode.

When `fsPolicy.workspaceOnly` is true, restrict `localRoots` to only the
workspace directory so that files outside the workspace are rejected by
`assertLocalMediaAllowed()`.

Relates to openclaw#31716

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix: add changelog credit for fsPolicy image/pdf propagation (openclaw#31882) (thanks @justinhuangcode)

* fix: skip Telegram command sync when menu is unchanged (openclaw#32017)

Hash the command list and cache it to disk per account. On restart,
compare the current hash against the cached one and skip the
deleteMyCommands + setMyCommands round-trip when nothing changed.
This prevents 429 rate-limit errors when the gateway restarts
several times in quick succession.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

* fix(telegram): scope command-sync hash cache by bot identity (openclaw#32059)

* fix: normalize coding-plan providers in auth order validation

* feat(security): Harden Docker browser container chromium flags (openclaw#23889) (openclaw#31504)

* Gateway: honor OPENCLAW_GATEWAY_URL override for remote/local calls

* Agents: fix sandbox sessionKey usage for PI embedded subagent calls

* Sandbox: tighten browser container Chromium runtime flags

* fix: add sandbox browser defaults for container hardening

* docs: expand sandbox browser default flags list

* fix: make sandbox browser flags optional and preserve gateway env auth overrides

* docs: scope PR 31504 changelog entry

* style: format gateway call override handling

* fix: dedupe sandbox browser chrome args

* fix: preserve remote tls fingerprint for env gateway override

* fix: enforce auth for env gateway URL override

* chore: document gateway override auth security expectations

* fix(delivery): strip HTML tags for plain-text messaging surfaces

Models occasionally produce HTML tags in their output. While these render
fine on web surfaces, they appear as literal text on WhatsApp, Signal,
SMS, IRC, and Telegram.

Add sanitizeForPlainText() utility that converts common inline HTML to
lightweight-markup equivalents and strips remaining tags. Applied in the
outbound delivery pipeline for non-HTML surfaces only.

Closes openclaw#31884
See also: openclaw#18558

* fix(outbound): harden plain-text HTML sanitization paths (openclaw#32034)

* fix(security): harden file installs and race-path tests

* matrix: bootstrap crypto runtime when npm scripts are skipped

* fix(matrix): keep plugin register sync while bootstrapping crypto runtime (openclaw#31989)

* perf(runtime): reduce cron persistence and logger overhead

* test(perf): use prebuilt plugin install archive fixtures

* test(perf): increase guardrail scan read concurrency

* fix(queue): restart drain when message enqueued after idle window

After a drain loop empties the queue it deletes the key from
FOLLOWUP_QUEUES.  If a new message arrives at that moment
enqueueFollowupRun creates a fresh queue object with draining:false
but never starts a drain, leaving the message stranded until the
next run completes and calls finalizeWithFollowup.

Fix: persist the most recent runFollowup callback per queue key in
FOLLOWUP_RUN_CALLBACKS (drain.ts).  enqueueFollowupRun now calls
kickFollowupDrainIfIdle after a successful push; if a cached
callback exists and no drain is running it calls scheduleFollowupDrain
to restart immediately.  clearSessionQueues cleans up the callback
cache alongside the queue state.

* fix: avoid stale followup drain callbacks (openclaw#31902) (thanks @Lanfei)

* fix(synology-chat): read cfg from outbound context so incomingUrl resolves

* fix: require openclaw.extensions for plugin installs (openclaw#32055) (thanks @liuxiaopai-ai)

---------

Co-authored-by: Andrii Furmanets <furmanets.andriy@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Saurabh <skmishra1991@gmail.com>
Co-authored-by: stone-jin <1520006273@qq.com>
Co-authored-by: scotthuang <scotthuang@tencent.com>
Co-authored-by: User <user@example.com>
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: justinhuangcode <justinhuangcode@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: AytuncYildizli <cryptosquanch@gmail.com>
Co-authored-by: bmendonca3 <bmendonca3@users.noreply.github.com>
Co-authored-by: Jealous <CooLanfei@163.com>
Co-authored-by: white-rm <zhang.xujin@xydigit.com>
zooqueen pushed a commit to hanzoai/bot that referenced this pull request Mar 6, 2026
* fix(plugins): fallback install entrypoints for legacy manifests

* Voice Call: enforce exact webhook path match

* Tests: isolate webhook path suite and reset cron auth state

* chore: keep openclaw#31930 scoped to voice webhook path fix

* fix: add changelog for exact voice webhook path match (openclaw#31930) (thanks @afurm)

* fix: handle HTTP 529 (Anthropic overloaded) in failover error classification

Classify Anthropic's 529 status code as "rate_limit" so model fallback
triggers reliably without depending on fragile message-based detection.

Closes openclaw#28502

* fix: add changelog for HTTP 529 failover classification (openclaw#31854) (thanks @bugkill3r)

* fix(slack): guard against undefined text in includes calls during mention handling

* fix: add changelog for mentions/slack null-safe guards (openclaw#31865) (thanks @stone-jin)

* fix(memory-lancedb): pass dimensions to embedding API call

- Add dimensions parameter to Embeddings constructor
- Pass dimensions to OpenAI embeddings.create() API call
- Fixes dimension mismatch when using custom embedding models like DashScope text-embedding-v4

* fix: add regression for memory-lancedb dimensions pass-through (openclaw#32036) (thanks @scotthuang)

* fix(telegram): guard malformed native menu specs

* fix: harden plugin command registration + telegram menu guard (openclaw#31997) (thanks @liuxiaopai-ai)

* fix(gateway): restart heartbeat on model config changes

* fix: add changelog credit for heartbeat model reload (openclaw#32046) (thanks @stakeswky)

* test(process): replace no-output timer subprocess with spawn mock

* test(perf): trim repeated setup in cron memory and config suites

* test(perf): reduce per-case setup in script and git-hook tests

* fix(slack): scope debounce key by message timestamp to prevent cross-thread collisions

Top-level channel messages from the same sender shared a bare channel
debounce key, causing concurrent messages in different threads to merge
into a single reply on the wrong thread. Now the debounce key includes
the message timestamp for top-level messages, matching how the downstream
session layer already scopes by canonicalThreadId.

Extracted buildSlackDebounceKey() for testability.

Closes openclaw#31935


* fix: harden slack debounce key routing and ordering (openclaw#31951) (thanks @scoootscooob)

* fix(openrouter): skip reasoning.effort injection for x-ai/grok models

x-ai/grok models on OpenRouter do not support the reasoning.effort
parameter and reject payloads containing it with "Invalid arguments
passed to the model." Skip reasoning injection for these models, the
same way we already skip it for the dynamic "auto" routing model.

Closes openclaw#32039


* fix: add changelog credit for openrouter x-ai reasoning guard (openclaw#32054) (thanks @scoootscooob)

* fix(agents): scope volcengine-plan/byteplus-plan auth lookup to profile resolution

The configure flow stores auth credentials under `provider: "volcengine"`,
but the coding model uses `volcengine-plan` as its provider. Add a scoped
`normalizeProviderIdForAuth` function used only by `listProfilesForProvider`
so coding-plan variants resolve to their base provider for auth credential
lookup without affecting global provider routing.

Closes openclaw#31731


* fix(tools): honor fsPolicy.workspaceOnly in image/pdf tool localRoots

PR openclaw#28822 fixed the Write/Edit tools to respect `tools.fs.workspaceOnly`,
but the image and PDF tools still unconditionally include default local
roots (`~/.openclaw/media`, `~/.openclaw/agents`, etc.) when computing
the `localRoots` allowlist for non-sandbox mode.

When `fsPolicy.workspaceOnly` is true, restrict `localRoots` to only the
workspace directory so that files outside the workspace are rejected by
`assertLocalMediaAllowed()`.

Relates to openclaw#31716


* fix: add changelog credit for fsPolicy image/pdf propagation (openclaw#31882) (thanks @justinhuangcode)

* fix: skip Telegram command sync when menu is unchanged (openclaw#32017)

Hash the command list and cache it to disk per account. On restart,
compare the current hash against the cached one and skip the
deleteMyCommands + setMyCommands round-trip when nothing changed.
This prevents 429 rate-limit errors when the gateway restarts
several times in quick succession.


* fix(telegram): scope command-sync hash cache by bot identity (openclaw#32059)

* fix: normalize coding-plan providers in auth order validation

* feat(security): Harden Docker browser container chromium flags (openclaw#23889) (openclaw#31504)

* Gateway: honor OPENCLAW_GATEWAY_URL override for remote/local calls

* Agents: fix sandbox sessionKey usage for PI embedded subagent calls

* Sandbox: tighten browser container Chromium runtime flags

* fix: add sandbox browser defaults for container hardening

* docs: expand sandbox browser default flags list

* fix: make sandbox browser flags optional and preserve gateway env auth overrides

* docs: scope PR 31504 changelog entry

* style: format gateway call override handling

* fix: dedupe sandbox browser chrome args

* fix: preserve remote tls fingerprint for env gateway override

* fix: enforce auth for env gateway URL override

* chore: document gateway override auth security expectations

* fix(delivery): strip HTML tags for plain-text messaging surfaces

Models occasionally produce HTML tags in their output. While these render
fine on web surfaces, they appear as literal text on WhatsApp, Signal,
SMS, IRC, and Telegram.

Add sanitizeForPlainText() utility that converts common inline HTML to
lightweight-markup equivalents and strips remaining tags. Applied in the
outbound delivery pipeline for non-HTML surfaces only.

Closes openclaw#31884
See also: openclaw#18558

* fix(outbound): harden plain-text HTML sanitization paths (openclaw#32034)

* fix(security): harden file installs and race-path tests

* matrix: bootstrap crypto runtime when npm scripts are skipped

* fix(matrix): keep plugin register sync while bootstrapping crypto runtime (openclaw#31989)

* perf(runtime): reduce cron persistence and logger overhead

* test(perf): use prebuilt plugin install archive fixtures

* test(perf): increase guardrail scan read concurrency

* fix(queue): restart drain when message enqueued after idle window

After a drain loop empties the queue it deletes the key from
FOLLOWUP_QUEUES.  If a new message arrives at that moment
enqueueFollowupRun creates a fresh queue object with draining:false
but never starts a drain, leaving the message stranded until the
next run completes and calls finalizeWithFollowup.

Fix: persist the most recent runFollowup callback per queue key in
FOLLOWUP_RUN_CALLBACKS (drain.ts).  enqueueFollowupRun now calls
kickFollowupDrainIfIdle after a successful push; if a cached
callback exists and no drain is running it calls scheduleFollowupDrain
to restart immediately.  clearSessionQueues cleans up the callback
cache alongside the queue state.

* fix: avoid stale followup drain callbacks (openclaw#31902) (thanks @Lanfei)

* fix(synology-chat): read cfg from outbound context so incomingUrl resolves

* fix: require openclaw.extensions for plugin installs (openclaw#32055) (thanks @liuxiaopai-ai)

---------

Co-authored-by: Andrii Furmanets <furmanets.andriy@gmail.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: Saurabh <skmishra1991@gmail.com>
Co-authored-by: stone-jin <1520006273@qq.com>
Co-authored-by: scotthuang <scotthuang@tencent.com>
Co-authored-by: User <user@example.com>
Co-authored-by: scoootscooob <zhentongfan@gmail.com>
Co-authored-by: justinhuangcode <justinhuangcode@users.noreply.github.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Co-authored-by: AytuncYildizli <cryptosquanch@gmail.com>
Co-authored-by: bmendonca3 <bmendonca3@users.noreply.github.com>
Co-authored-by: Jealous <CooLanfei@163.com>
Co-authored-by: white-rm <zhang.xujin@xydigit.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

channel: telegram Channel integration: telegram size: S

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Telegram 429 rate limit on gateway restart — setMyCommands/deleteMyCommands causes bot lockout

2 participants