From 53e9ae84cc77781eb97366b8ff4eabd42ec4c694 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Wed, 6 May 2026 19:00:54 +0100 Subject: [PATCH] refactor: drop stale session load options --- src/agents/live-model-switch.ts | 4 ++-- src/auto-reply/reply/agent-runner.ts | 2 +- src/auto-reply/reply/commands-export-common.ts | 2 +- src/auto-reply/reply/dispatch-acp-transcript.runtime.ts | 2 +- src/auto-reply/reply/get-reply-fast-path.ts | 4 +--- src/auto-reply/reply/session.test.ts | 5 ++--- src/auto-reply/reply/session.ts | 8 +------- src/commands/export-trajectory.ts | 2 +- src/config/sessions/cleanup-service.ts | 4 ++-- src/config/sessions/combined-store-gateway.ts | 4 ++-- src/config/sessions/store.ts | 2 +- src/config/sessions/transcript.ts | 2 +- src/cron/session-reaper.ts | 2 +- src/gateway/boot.ts | 2 +- src/tasks/task-registry.maintenance.ts | 4 ++-- 15 files changed, 20 insertions(+), 29 deletions(-) diff --git a/src/agents/live-model-switch.ts b/src/agents/live-model-switch.ts index ee4e38a4b4a..e31f6213158 100644 --- a/src/agents/live-model-switch.ts +++ b/src/agents/live-model-switch.ts @@ -37,7 +37,7 @@ export function resolveLiveSessionModelSelection(params: { const storePath = resolveStorePath(cfg.session?.store, { agentId, }); - const entry = loadSessionStore(storePath, { skipCache: true })[sessionKey]; + const entry = loadSessionStore(storePath)[sessionKey]; const normalizedSelection = normalizeStoredOverrideModel({ providerOverride: entry?.providerOverride, modelOverride: entry?.modelOverride, @@ -159,7 +159,7 @@ export function shouldSwitchToLiveModel(params: { const storePath = resolveStorePath(cfg.session?.store, { agentId: params.agentId?.trim(), }); - const entry = loadSessionStore(storePath, { skipCache: true })[sessionKey]; + const entry = loadSessionStore(storePath)[sessionKey]; if (!entry?.liveModelSwitchPending) { return undefined; } diff --git a/src/auto-reply/reply/agent-runner.ts b/src/auto-reply/reply/agent-runner.ts index 0c9253171c8..ffe4b85bd62 100644 --- a/src/auto-reply/reply/agent-runner.ts +++ b/src/auto-reply/reply/agent-runner.ts @@ -896,7 +896,7 @@ function refreshSessionEntryFromStore(params: { return fallbackEntry; } try { - const latestStore = loadSessionStore(storePath, { skipCache: true }); + const latestStore = loadSessionStore(storePath); const latestEntry = latestStore?.[sessionKey]; if (!latestEntry) { return fallbackEntry; diff --git a/src/auto-reply/reply/commands-export-common.ts b/src/auto-reply/reply/commands-export-common.ts index eeb8d1f8c85..084ef8891db 100644 --- a/src/auto-reply/reply/commands-export-common.ts +++ b/src/auto-reply/reply/commands-export-common.ts @@ -46,7 +46,7 @@ export function resolveExportCommandSessionTarget( ): ExportCommandSessionTarget | ReplyPayload { const targetAgentId = resolveAgentIdFromSessionKey(params.sessionKey) || params.agentId || "main"; const storePath = params.storePath ?? resolveDefaultSessionStorePath(targetAgentId); - const store = loadSessionStore(storePath, { skipCache: true }); + const store = loadSessionStore(storePath); const entry = store[params.sessionKey] as SessionEntry | undefined; if (!entry?.sessionId) { return { text: `❌ Session not found: ${params.sessionKey}` }; diff --git a/src/auto-reply/reply/dispatch-acp-transcript.runtime.ts b/src/auto-reply/reply/dispatch-acp-transcript.runtime.ts index 9d80dc0c035..e148f67c0de 100644 --- a/src/auto-reply/reply/dispatch-acp-transcript.runtime.ts +++ b/src/auto-reply/reply/dispatch-acp-transcript.runtime.ts @@ -30,7 +30,7 @@ export async function persistAcpDispatchTranscript(params: { const storePath = resolveStorePath(params.cfg.session?.store, { agentId: sessionAgentId, }); - const sessionStore = loadSessionStore(storePath, { skipCache: true }); + const sessionStore = loadSessionStore(storePath); const sessionEntry = resolveSessionStoreEntry({ store: sessionStore, sessionKey: params.sessionKey, diff --git a/src/auto-reply/reply/get-reply-fast-path.ts b/src/auto-reply/reply/get-reply-fast-path.ts index c92551fa448..82cf32bafcb 100644 --- a/src/auto-reply/reply/get-reply-fast-path.ts +++ b/src/auto-reply/reply/get-reply-fast-path.ts @@ -213,9 +213,7 @@ export function initFastReplySessionState(params: { mainKey: cfg.session?.mainKey, }); const storePath = resolveStorePath(cfg.session?.store, { agentId }); - const sessionStore: Record = loadSessionStore(storePath, { - skipCache: true, - }); + const sessionStore: Record = loadSessionStore(storePath); const existingEntry = sessionStore[sessionKey]; const commandSource = ctx.BodyForCommands ?? ctx.CommandBody ?? ctx.RawBody ?? ctx.Body ?? ""; const triggerBodyNormalized = stripStructuralPrefixes(commandSource).trim(); diff --git a/src/auto-reply/reply/session.test.ts b/src/auto-reply/reply/session.test.ts index fd12a9e4dcd..d4e7a5e0566 100644 --- a/src/auto-reply/reply/session.test.ts +++ b/src/auto-reply/reply/session.test.ts @@ -8,7 +8,7 @@ import { getOrCreateSessionMcpRuntime, } from "../../agents/pi-bundle-mcp-tools.js"; import type { OpenClawConfig } from "../../config/config.js"; -import type { SessionEntry } from "../../config/sessions.js"; +import { saveSessionStore, type SessionEntry } from "../../config/sessions.js"; import { formatZonedTimestamp } from "../../infra/format-time/format-datetime.ts"; import { __testing as sessionBindingTesting, @@ -1229,8 +1229,7 @@ describe("initSessionState RawBody", () => { vi.stubEnv("OPENCLAW_STATE_DIR", stateDir); try { - await fs.mkdir(path.dirname(storePath), { recursive: true }); - await writeSessionStoreFast(storePath, { + await saveSessionStore(storePath, { [sessionKey]: { sessionId, sessionFile, diff --git a/src/auto-reply/reply/session.ts b/src/auto-reply/reply/session.ts index 9c79a28861e..3725ca1453f 100644 --- a/src/auto-reply/reply/session.ts +++ b/src/auto-reply/reply/session.ts @@ -271,14 +271,8 @@ export async function initSessionState(params: { const storePath = resolveStorePath(sessionCfg?.store, { agentId }); const ingressTimingEnabled = process.env.OPENCLAW_DEBUG_INGRESS_TIMING === "1"; - // CRITICAL: Skip cache to ensure fresh data when resolving session identity. - // Stale cache (especially with multiple gateway processes or on Windows where - // mtime granularity may miss rapid writes) can cause incorrect sessionId - // generation, leading to orphaned transcript files. See #17971. const sessionStoreLoadStartMs = ingressTimingEnabled ? Date.now() : 0; - const sessionStore: Record = loadSessionStore(storePath, { - skipCache: true, - }); + const sessionStore: Record = loadSessionStore(storePath); if (ingressTimingEnabled) { log.info( `session-init store-load agent=${agentId} session=${sessionCtxForState.SessionKey ?? "(no-session)"} ` + diff --git a/src/commands/export-trajectory.ts b/src/commands/export-trajectory.ts index f175602c948..34c752a2801 100644 --- a/src/commands/export-trajectory.ts +++ b/src/commands/export-trajectory.ts @@ -97,7 +97,7 @@ export async function exportTrajectoryCommand( const storePath = resolvedOpts.store ? path.resolve(resolvedOpts.store) : resolveDefaultSessionStorePath(targetAgentId); - const store = loadSessionStore(storePath, { skipCache: true }); + const store = loadSessionStore(storePath); const entry = store[sessionKey] as SessionEntry | undefined; if (!entry?.sessionId) { runtime.error( diff --git a/src/config/sessions/cleanup-service.ts b/src/config/sessions/cleanup-service.ts index 5d356c38810..3c2ba79b7ae 100644 --- a/src/config/sessions/cleanup-service.ts +++ b/src/config/sessions/cleanup-service.ts @@ -251,7 +251,7 @@ async function previewStoreCleanup(params: { fixMissing?: boolean; fixDmScope?: boolean; }) { - const beforeStore = loadSessionStore(params.target.storePath, { skipCache: true }); + const beforeStore = loadSessionStore(params.target.storePath); const previewStore = structuredClone(beforeStore); const staleKeys = new Set(); const cappedKeys = new Set(); @@ -450,7 +450,7 @@ export async function runSessionsCleanup(params: { }; return missing; }); - const afterStore = loadSessionStore(target.storePath, { skipCache: true }); + const afterStore = loadSessionStore(target.storePath); const unreferencedArtifacts = mode === "warn" ? { diff --git a/src/config/sessions/combined-store-gateway.ts b/src/config/sessions/combined-store-gateway.ts index 7840b5944a6..a0f8e81f882 100644 --- a/src/config/sessions/combined-store-gateway.ts +++ b/src/config/sessions/combined-store-gateway.ts @@ -68,7 +68,7 @@ export function loadCombinedSessionStoreForGateway( if (storeConfig && !isStorePathTemplate(storeConfig)) { const storePath = resolveStorePath(storeConfig); const defaultAgentId = normalizeAgentId(resolveDefaultAgentId(cfg)); - const store = loadSessionStore(storePath, { clone: false }); + const store = loadSessionStore(storePath); const combined: Record = {}; for (const [key, entry] of Object.entries(store)) { const canonicalKey = resolveStoredSessionKeyForAgentStore({ @@ -98,7 +98,7 @@ export function loadCombinedSessionStoreForGateway( for (const target of targets) { const agentId = target.agentId; const storePath = target.storePath; - const store = loadSessionStore(storePath, { clone: false }); + const store = loadSessionStore(storePath); for (const [key, entry] of Object.entries(store)) { const canonicalKey = resolveStoredSessionKeyForAgentStore({ cfg, diff --git a/src/config/sessions/store.ts b/src/config/sessions/store.ts index d999bdf0ce7..a88b40faa86 100644 --- a/src/config/sessions/store.ts +++ b/src/config/sessions/store.ts @@ -79,7 +79,7 @@ type SaveSessionStoreOptions = { }; function loadMutableSessionStoreForWriter(storePath: string): Record { - return loadSessionStore(storePath, { skipCache: true, clone: false }); + return loadSessionStore(storePath); } function resolveMutableSessionStoreKey( diff --git a/src/config/sessions/transcript.ts b/src/config/sessions/transcript.ts index 89c92e56df3..050741e5c6c 100644 --- a/src/config/sessions/transcript.ts +++ b/src/config/sessions/transcript.ts @@ -319,7 +319,7 @@ export async function appendExactAssistantMessageToSessionTranscript(params: { } const storePath = params.storePath ?? resolveDefaultSessionStorePath(params.agentId); - const store = loadSessionStore(storePath, { skipCache: true }); + const store = loadSessionStore(storePath); const normalizedKey = normalizeStoreSessionKey(sessionKey); const entry = (store[normalizedKey] ?? store[sessionKey]) as SessionEntry | undefined; if (!entry?.sessionId) { diff --git a/src/cron/session-reaper.ts b/src/cron/session-reaper.ts index 8de393931bd..59946582d01 100644 --- a/src/cron/session-reaper.ts +++ b/src/cron/session-reaper.ts @@ -107,7 +107,7 @@ export async function sweepCronRunSessions(params: { if (prunedSessions.size > 0) { try { - const store = loadSessionStore(storePath, { skipCache: true }); + const store = loadSessionStore(storePath); const referencedSessionIds = new Set( Object.values(store) .map((entry) => entry?.sessionId) diff --git a/src/gateway/boot.ts b/src/gateway/boot.ts index eb70ee58ee7..c8a172c03f3 100644 --- a/src/gateway/boot.ts +++ b/src/gateway/boot.ts @@ -81,7 +81,7 @@ function snapshotMainSessionMapping(params: { const agentId = resolveAgentIdFromSessionKey(params.sessionKey); const storePath = resolveStorePath(params.cfg.session?.store, { agentId }); try { - const store = loadSessionStore(storePath, { skipCache: true }); + const store = loadSessionStore(storePath); const entry = store[params.sessionKey]; if (!entry) { return { diff --git a/src/tasks/task-registry.maintenance.ts b/src/tasks/task-registry.maintenance.ts index 26ccfcb15ea..b886275c4da 100644 --- a/src/tasks/task-registry.maintenance.ts +++ b/src/tasks/task-registry.maintenance.ts @@ -206,7 +206,7 @@ function getSessionStoreLookup( ): SessionStoreLookup { if (!context) { return { - store: taskRegistryMaintenanceRuntime.loadSessionStore(storePath, { clone: false }), + store: taskRegistryMaintenanceRuntime.loadSessionStore(storePath), }; } const cached = context.sessionStoresByPath.get(storePath); @@ -214,7 +214,7 @@ function getSessionStoreLookup( return cached; } const lookup = { - store: taskRegistryMaintenanceRuntime.loadSessionStore(storePath, { clone: false }), + store: taskRegistryMaintenanceRuntime.loadSessionStore(storePath), }; context.sessionStoresByPath.set(storePath, lookup); return lookup;