From af9d76b79a1e0a1ba88780ffbcc54b87e81c3ae8 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sun, 8 Mar 2026 02:33:52 +0000 Subject: [PATCH] fix: honor explicit Synology Chat rate-limit env values Landed from contributor PR #39197 by @scoootscooob. Co-authored-by: scoootscooob --- CHANGELOG.md | 1 + extensions/synology-chat/src/accounts.test.ts | 14 ++++++++++++++ extensions/synology-chat/src/accounts.ts | 17 +++++++++++++---- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a86393d8e29..8bffbf047dc 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -69,6 +69,7 @@ Docs: https://docs.openclaw.ai - Agents/fallback cooldown probe execution: thread explicit rate-limit cooldown probe intent from model fallback into embedded runner auth-profile selection so same-provider fallback attempts can actually run when all profiles are cooldowned for `rate_limit` (instead of failing pre-run as `No available auth profile`), while preserving default cooldown skip behavior and adding regression tests at both fallback and runner layers. (#13623) Thanks @asfura. - Cron/OpenAI Codex OAuth refresh hardening: when `openai-codex` token refresh fails specifically on account-id extraction, reuse the cached access token instead of failing the run immediately, with regression coverage to keep non-Codex and unrelated refresh failures unchanged. (#36604) Thanks @laulopezreal. - TUI/session isolation for `/new`: make `/new` allocate a unique `tui-` session key instead of resetting the shared agent session, so multiple TUI clients on the same agent stop receiving each other’s replies; also sanitize `/new` and `/reset` failure text before rendering in-terminal. Landed from contributor PR #39238 by @widingmarcus-cyber. Thanks @widingmarcus-cyber. +- Synology Chat/rate-limit env parsing: honor `SYNOLOGY_RATE_LIMIT=0` as an explicit value while still falling back to the default limit for malformed env values instead of partially parsing them. Landed from contributor PR #39197 by @scoootscooob. Thanks @scoootscooob. - Cron/file permission hardening: enforce owner-only (`0600`) cron store/backup/run-log files and harden cron store + run-log directories to `0700`, including pre-existing directories from older installs. (#36078) Thanks @aerelune. - Gateway/remote WS break-glass hostname support: honor `OPENCLAW_ALLOW_INSECURE_PRIVATE_WS=1` for `ws://` hostname URLs (not only private IP literals) across onboarding validation and runtime gateway connection checks, while still rejecting public IP literals and non-unicast IPv6 endpoints. (#36930) Thanks @manju-rn. - Routing/binding lookup scalability: pre-index route bindings by channel/account and avoid full binding-list rescans on channel-account cache rollover, preventing multi-second `resolveAgentRoute` stalls in large binding configurations. (#36915) Thanks @songchenghao. diff --git a/extensions/synology-chat/src/accounts.test.ts b/extensions/synology-chat/src/accounts.test.ts index 71dab24defe..627afb37378 100644 --- a/extensions/synology-chat/src/accounts.test.ts +++ b/extensions/synology-chat/src/accounts.test.ts @@ -130,4 +130,18 @@ describe("resolveAccount", () => { const account = resolveAccount(cfg); expect(account.allowedUserIds).toEqual(["u1", "u2"]); }); + + it("respects SYNOLOGY_RATE_LIMIT=0 instead of defaulting to 30", () => { + process.env.SYNOLOGY_RATE_LIMIT = "0"; + const cfg = { channels: { "synology-chat": {} } }; + const account = resolveAccount(cfg); + expect(account.rateLimitPerMinute).toBe(0); + }); + + it("falls back to 30 for malformed SYNOLOGY_RATE_LIMIT values", () => { + process.env.SYNOLOGY_RATE_LIMIT = "0abc"; + const cfg = { channels: { "synology-chat": {} } }; + const account = resolveAccount(cfg); + expect(account.rateLimitPerMinute).toBe(30); + }); }); diff --git a/extensions/synology-chat/src/accounts.ts b/extensions/synology-chat/src/accounts.ts index 1239e733f5a..483aa5944e8 100644 --- a/extensions/synology-chat/src/accounts.ts +++ b/extensions/synology-chat/src/accounts.ts @@ -20,6 +20,17 @@ function parseAllowedUserIds(raw: string | string[] | undefined): string[] { .filter(Boolean); } +function parseRateLimitPerMinute(raw: string | undefined): number { + if (raw == null) { + return 30; + } + const trimmed = raw.trim(); + if (!/^-?\d+$/.test(trimmed)) { + return 30; + } + return Number.parseInt(trimmed, 10); +} + /** * List all configured account IDs for this channel. * Returns ["default"] if there's a base config, plus any named accounts. @@ -62,7 +73,7 @@ export function resolveAccount(cfg: any, accountId?: string | null): ResolvedSyn const envIncomingUrl = process.env.SYNOLOGY_CHAT_INCOMING_URL ?? ""; const envNasHost = process.env.SYNOLOGY_NAS_HOST ?? "localhost"; const envAllowedUserIds = process.env.SYNOLOGY_ALLOWED_USER_IDS ?? ""; - const envRateLimit = process.env.SYNOLOGY_RATE_LIMIT; + const envRateLimitValue = parseRateLimitPerMinute(process.env.SYNOLOGY_RATE_LIMIT); const envBotName = process.env.OPENCLAW_BOT_NAME ?? "OpenClaw"; // Merge: account override > base channel config > env var @@ -78,9 +89,7 @@ export function resolveAccount(cfg: any, accountId?: string | null): ResolvedSyn accountOverride.allowedUserIds ?? channelCfg.allowedUserIds ?? envAllowedUserIds, ), rateLimitPerMinute: - accountOverride.rateLimitPerMinute ?? - channelCfg.rateLimitPerMinute ?? - (envRateLimit ? parseInt(envRateLimit, 10) || 30 : 30), + accountOverride.rateLimitPerMinute ?? channelCfg.rateLimitPerMinute ?? envRateLimitValue, botName: accountOverride.botName ?? channelCfg.botName ?? envBotName, allowInsecureSsl: accountOverride.allowInsecureSsl ?? channelCfg.allowInsecureSsl ?? false, };