From c5aeee1d5c23b2703d4d40d0c7b8a8a3eb484dbc Mon Sep 17 00:00:00 2001 From: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> Date: Thu, 9 Jul 2026 13:36:09 +0000 Subject: [PATCH] fix: clear session suspension timers on gateway shutdown --- src/agents/session-suspension.test.ts | 9 +++------ src/agents/session-suspension.ts | 24 +++++++++++------------- 2 files changed, 14 insertions(+), 19 deletions(-) diff --git a/src/agents/session-suspension.test.ts b/src/agents/session-suspension.test.ts index 3d152958210..38aaba95622 100644 --- a/src/agents/session-suspension.test.ts +++ b/src/agents/session-suspension.test.ts @@ -1,7 +1,6 @@ // Verifies quota suspension persists lane state and auto-resumes safely. import { afterEach, describe, expect, it, vi } from "vitest"; import { DEFAULT_CRON_MAX_CONCURRENT_RUNS } from "../config/cron-limits.js"; -import type { QuotaSuspension } from "../config/sessions/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { CommandLane } from "../process/lanes.js"; import { MAX_TIMER_TIMEOUT_MS } from "../shared/number-coercion.js"; @@ -135,10 +134,8 @@ describe("session suspension", () => { await suspendLane(Number.MAX_SAFE_INTEGER, {} as OpenClawConfig, CommandLane.Main); expect(setTimeoutSpy).toHaveBeenCalledWith(expect.any(Function), MAX_TIMER_TIMEOUT_MS); - const patch = sessionStoreMocks.applySessionStoreEntryPatch.mock.calls[0]?.[0].patch as { - quotaSuspension?: QuotaSuspension; - }; - const expectedSuspension: QuotaSuspension = { + const patch = sessionStoreMocks.applySessionStoreEntryPatch.mock.calls[0]?.[0].patch; + const expectedSuspension = { schemaVersion: 1, suspendedAt: 1_000, reason: "quota_exhausted", @@ -147,7 +144,7 @@ describe("session suspension", () => { laneId: CommandLane.Main, expectedResumeBy: 1_000 + MAX_TIMER_TIMEOUT_MS, state: "suspended", - }; + } as const; expect(patch.quotaSuspension).toMatchObject(expectedSuspension); expect(patch.quotaSuspension).toStrictEqual({ ...expectedSuspension, diff --git a/src/agents/session-suspension.ts b/src/agents/session-suspension.ts index 34b5614f9d6..02416e78f21 100644 --- a/src/agents/session-suspension.ts +++ b/src/agents/session-suspension.ts @@ -8,7 +8,6 @@ import path from "node:path"; import { resolveAgentMaxConcurrent, resolveSubagentMaxConcurrent } from "../config/agent-limits.js"; import { resolveCronMaxConcurrentRuns } from "../config/cron-limits.js"; import { applySessionStoreEntryPatch } from "../config/sessions.js"; -import type { QuotaSuspension } from "../config/sessions/types.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; import { createSubsystemLogger } from "../logging/subsystem.js"; import { setCommandLaneConcurrency } from "../process/command-queue.js"; @@ -133,17 +132,6 @@ export async function suspendSession(params: SessionSuspensionParams) { const ttlMs = resolveTimerTimeoutMs(params.ttlMs, DEFAULT_QUOTA_SUSPENSION_RESUME_MS, 0); const now = Date.now(); const expectedResumeBy = resolveExpiresAtMsFromDurationMs(ttlMs, { nowMs: now }) ?? now; - const quotaSuspension: QuotaSuspension = { - schemaVersion: 1, - suspendedAt: now, - reason: params.reason, - failedProvider: params.failedProvider, - failedModel: params.failedModel, - summary: params.summary, - laneId: params.laneId, - expectedResumeBy, - state: "suspended", - }; try { await applySessionStoreEntryPatch({ @@ -152,7 +140,17 @@ export async function suspendSession(params: SessionSuspensionParams) { skipMaintenance: true, takeCacheOwnership: true, patch: { - quotaSuspension, + quotaSuspension: { + schemaVersion: 1, + suspendedAt: now, + reason: params.reason, + failedProvider: params.failedProvider, + failedModel: params.failedModel, + summary: params.summary, + laneId: params.laneId, + expectedResumeBy, + state: "suspended", + }, }, }); } catch (err) {