From 6a21962552043925fe7c67530a92077bd09acfcc Mon Sep 17 00:00:00 2001 From: Heather Wilde Renze Date: Mon, 20 Apr 2026 10:21:36 -0700 Subject: [PATCH] fix(sessions): enforce maintenance by default and prune on load to prevent gateway OOM Co-authored-by: bobrenze-bot --- CHANGELOG.md | 1 + src/config/sessions/store-load.ts | 23 +++++++++++++++++++++++ src/config/sessions/store-maintenance.ts | 2 +- 3 files changed, 25 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index f376e6e6849..a048e4fceb3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ Docs: https://docs.openclaw.ai ### Changes - Models/costs: support tiered model pricing from cached catalogs and configured models, and include bundled Moonshot Kimi K2.6/K2.5 cost estimates for token-usage reports. (#67605) Thanks @sliverp. +- Sessions/Maintenance: enforce the built-in entry cap and age prune by default, and prune oversized stores at load time so accumulated cron/executor session backlogs cannot OOM the gateway before the write path runs. (#69404) Thanks @bobrenze-bot. - Plugins/tests: reuse plugin loader alias and Jiti config resolution across repeated same-context loads, reducing import-heavy test overhead. (#69316) Thanks @amknight. - Cron: split runtime execution state into `jobs-state.json` so `jobs.json` stays stable for git-tracked job definitions. (#63105) Thanks @Feelw00. - Agents/compaction: send opt-in start and completion notices during context compaction. (#67830) Thanks @feniix. diff --git a/src/config/sessions/store-load.ts b/src/config/sessions/store-load.ts index bd6490712e2..715959d0452 100644 --- a/src/config/sessions/store-load.ts +++ b/src/config/sessions/store-load.ts @@ -1,4 +1,5 @@ import fs from "node:fs"; +import { createSubsystemLogger } from "../../logging/subsystem.js"; import { normalizeSessionDeliveryFields } from "../../utils/delivery-context.shared.js"; import { getFileStatSnapshot } from "../cache-utils.js"; import { @@ -8,12 +9,15 @@ import { writeSessionStoreCache, } from "./store-cache.js"; import { applySessionStoreMigrations } from "./store-migrations.js"; +import { capEntryCount, pruneStaleEntries, resolveMaintenanceConfig } from "./store-maintenance.js"; import { normalizeSessionRuntimeModelFields, type SessionEntry } from "./types.js"; export type LoadSessionStoreOptions = { skipCache?: boolean; }; +const log = createSubsystemLogger("sessions/store"); + function isSessionStoreRecord(value: unknown): value is Record { return !!value && typeof value === "object" && !Array.isArray(value); } @@ -118,6 +122,25 @@ export function loadSessionStore( applySessionStoreMigrations(store); normalizeSessionStore(store); + const maintenance = resolveMaintenanceConfig(); + if (maintenance.mode === "enforce" && Object.keys(store).length > maintenance.maxEntries) { + const beforeCount = Object.keys(store).length; + const pruned = pruneStaleEntries(store, maintenance.pruneAfterMs, { log: false }); + const capped = capEntryCount(store, maintenance.maxEntries, { log: false }); + const afterCount = Object.keys(store).length; + if (pruned > 0 || capped > 0) { + serializedFromDisk = undefined; + setSerializedSessionStore(storePath, undefined); + log.info("applied load-time maintenance to oversized session store", { + storePath, + before: beforeCount, + after: afterCount, + pruned, + capped, + maxEntries: maintenance.maxEntries, + }); + } + } if (!opts.skipCache && isSessionStoreCacheEnabled()) { writeSessionStoreCache({ diff --git a/src/config/sessions/store-maintenance.ts b/src/config/sessions/store-maintenance.ts index 0ac73403c40..4a272100134 100644 --- a/src/config/sessions/store-maintenance.ts +++ b/src/config/sessions/store-maintenance.ts @@ -13,7 +13,7 @@ const log = createSubsystemLogger("sessions/store"); const DEFAULT_SESSION_PRUNE_AFTER_MS = 30 * 24 * 60 * 60 * 1000; const DEFAULT_SESSION_MAX_ENTRIES = 500; const DEFAULT_SESSION_ROTATE_BYTES = 10_485_760; // 10 MB -const DEFAULT_SESSION_MAINTENANCE_MODE: SessionMaintenanceMode = "warn"; +const DEFAULT_SESSION_MAINTENANCE_MODE: SessionMaintenanceMode = "enforce"; const DEFAULT_SESSION_DISK_BUDGET_HIGH_WATER_RATIO = 0.8; export type SessionMaintenanceWarning = {