diff --git a/src/gateway/config-reload.test.ts b/src/gateway/config-reload.test.ts index 25137aef0..8eee9df30 100644 --- a/src/gateway/config-reload.test.ts +++ b/src/gateway/config-reload.test.ts @@ -159,6 +159,12 @@ describe("buildGatewayReloadPlan", () => { expect(plan.noopPaths).toContain("secrets.providers.default.path"); }); + it("treats diagnostics.stuckSessionWarnMs as no-op for gateway restart planning", () => { + const plan = buildGatewayReloadPlan(["diagnostics.stuckSessionWarnMs"]); + expect(plan.restartGateway).toBe(false); + expect(plan.noopPaths).toContain("diagnostics.stuckSessionWarnMs"); + }); + it("defaults unknown paths to restart", () => { const plan = buildGatewayReloadPlan(["unknownField"]); expect(plan.restartGateway).toBe(true); diff --git a/src/gateway/config-reload.ts b/src/gateway/config-reload.ts index 3dedff84c..a1a89717a 100644 --- a/src/gateway/config-reload.ts +++ b/src/gateway/config-reload.ts @@ -50,6 +50,8 @@ const MISSING_CONFIG_MAX_RETRIES = 2; const BASE_RELOAD_RULES: ReloadRule[] = [ { prefix: "gateway.remote", kind: "none" }, { prefix: "gateway.reload", kind: "none" }, + // Stuck-session warning threshold is read by the diagnostics heartbeat loop. + { prefix: "diagnostics.stuckSessionWarnMs", kind: "none" }, { prefix: "hooks.gmail", kind: "hot", actions: ["restart-gmail-watcher"] }, { prefix: "hooks", kind: "hot", actions: ["reload-hooks"] }, { diff --git a/src/gateway/server.impl.ts b/src/gateway/server.impl.ts index e2bc0d0bc..414d4db75 100644 --- a/src/gateway/server.impl.ts +++ b/src/gateway/server.impl.ts @@ -372,7 +372,7 @@ export async function startGatewayServer( ).config; const diagnosticsEnabled = isDiagnosticsEnabled(cfgAtStart); if (diagnosticsEnabled) { - startDiagnosticHeartbeat(cfgAtStart); + startDiagnosticHeartbeat(); } setGatewaySigusr1RestartPolicy({ allowExternal: isRestartEnabled(cfgAtStart) }); setPreRestartDeferralCheck( diff --git a/src/logging/diagnostic.ts b/src/logging/diagnostic.ts index ffc36cf98..48f7da84d 100644 --- a/src/logging/diagnostic.ts +++ b/src/logging/diagnostic.ts @@ -1,3 +1,4 @@ +import { loadConfig } from "../config/config.js"; import type { OpenClawConfig } from "../config/config.js"; import { emitDiagnosticEvent } from "../infra/diagnostic-events.js"; import { @@ -325,8 +326,16 @@ export function startDiagnosticHeartbeat(config?: OpenClawConfig) { if (heartbeatInterval) { return; } - const stuckSessionWarnMs = resolveStuckSessionWarnMs(config); heartbeatInterval = setInterval(() => { + let heartbeatConfig = config; + if (!heartbeatConfig) { + try { + heartbeatConfig = loadConfig(); + } catch { + heartbeatConfig = undefined; + } + } + const stuckSessionWarnMs = resolveStuckSessionWarnMs(heartbeatConfig); const now = Date.now(); pruneDiagnosticSessionStates(now, true); const activeCount = Array.from(diagnosticSessionStates.values()).filter(