From b87d619072c7ca8444530e1f234203d6840cb432 Mon Sep 17 00:00:00 2001 From: bittoby <218712309+bittoby@users.noreply.github.com> Date: Thu, 16 Apr 2026 15:56:31 +0200 Subject: [PATCH] fix: share session purge across CLI and gateway agent delete paths (#65524) --- src/commands/agents.command-shared.ts | 22 +++++++++++++++ src/commands/agents.commands.delete.ts | 39 ++++++-------------------- src/gateway/server-methods/agents.ts | 4 +++ 3 files changed, 34 insertions(+), 31 deletions(-) diff --git a/src/commands/agents.command-shared.ts b/src/commands/agents.command-shared.ts index 3508047f3e1..c26d6064375 100644 --- a/src/commands/agents.command-shared.ts +++ b/src/commands/agents.command-shared.ts @@ -1,4 +1,7 @@ +import { resolveStorePath, updateSessionStore } from "../config/sessions.js"; import type { OpenClawConfig } from "../config/types.openclaw.js"; +import { getLogger } from "../logging/logger.js"; +import { resolveAgentIdFromSessionKey } from "../routing/session-key.js"; import type { RuntimeEnv } from "../runtime.js"; import { requireValidConfigFileSnapshot as requireValidConfigFileSnapshotBase, @@ -16,3 +19,22 @@ export async function requireValidConfigFileSnapshot(runtime: RuntimeEnv) { export async function requireValidConfig(runtime: RuntimeEnv): Promise { return await requireValidConfigSnapshot(runtime); } + +/** Purge session store entries for a deleted agent (#65524). Best-effort. */ +export async function purgeAgentSessionStoreEntries( + cfg: OpenClawConfig, + agentId: string, +): Promise { + try { + const storePath = resolveStorePath(cfg.session?.store, { agentId }); + await updateSessionStore(storePath, (store) => { + for (const key of Object.keys(store)) { + if (resolveAgentIdFromSessionKey(key) === agentId) { + delete store[key]; + } + } + }); + } catch (err) { + getLogger().debug("session store purge skipped during agent delete", err); + } +} diff --git a/src/commands/agents.commands.delete.ts b/src/commands/agents.commands.delete.ts index 18a5895b8f8..b7a28af57fb 100644 --- a/src/commands/agents.commands.delete.ts +++ b/src/commands/agents.commands.delete.ts @@ -1,22 +1,16 @@ import { resolveAgentDir, resolveAgentWorkspaceDir } from "../agents/agent-scope.js"; import { replaceConfigFile } from "../config/config.js"; import { logConfigUpdated } from "../config/logging.js"; -import { - loadSessionStore, - resolveSessionTranscriptsDirForAgent, - resolveStorePath, - updateSessionStore, -} from "../config/sessions.js"; -import { getLogger } from "../logging/logger.js"; -import { - DEFAULT_AGENT_ID, - normalizeAgentId, - resolveAgentIdFromSessionKey, -} from "../routing/session-key.js"; +import { resolveSessionTranscriptsDirForAgent } from "../config/sessions.js"; +import { DEFAULT_AGENT_ID, normalizeAgentId } from "../routing/session-key.js"; import { type RuntimeEnv, writeRuntimeJson } from "../runtime.js"; import { defaultRuntime } from "../runtime.js"; import { createClackPrompter } from "../wizard/clack-prompter.js"; -import { createQuietRuntime, requireValidConfigFileSnapshot } from "./agents.command-shared.js"; +import { + createQuietRuntime, + purgeAgentSessionStoreEntries, + requireValidConfigFileSnapshot, +} from "./agents.command-shared.js"; import { findAgentEntryIndex, listAgentEntries, pruneAgentConfig } from "./agents.config.js"; import { moveToTrash } from "./onboard-helpers.js"; @@ -91,24 +85,7 @@ export async function agentsDeleteCommand( } // Purge session store entries for this agent so orphaned sessions cannot be targeted (#65524). - try { - const storePath = resolveStorePath(cfg.session?.store, { agentId }); - const store = loadSessionStore(storePath); - const hasEntries = Object.keys(store).some( - (key) => resolveAgentIdFromSessionKey(key) === agentId, - ); - if (hasEntries) { - await updateSessionStore(storePath, (s) => { - for (const key of Object.keys(s)) { - if (resolveAgentIdFromSessionKey(key) === agentId) { - delete s[key]; - } - } - }); - } - } catch (err) { - getLogger().debug("session store purge skipped during agent delete", err); - } + await purgeAgentSessionStoreEntries(cfg, agentId); const quietRuntime = opts.json ? createQuietRuntime(runtime) : runtime; await moveToTrash(workspaceDir, quietRuntime); diff --git a/src/gateway/server-methods/agents.ts b/src/gateway/server-methods/agents.ts index d905876538e..b3df9a30471 100644 --- a/src/gateway/server-methods/agents.ts +++ b/src/gateway/server-methods/agents.ts @@ -20,6 +20,7 @@ import { ensureAgentWorkspace, isWorkspaceSetupCompleted, } from "../../agents/workspace.js"; +import { purgeAgentSessionStoreEntries } from "../../commands/agents.command-shared.js"; import { applyAgentConfig, findAgentEntryIndex, @@ -650,6 +651,9 @@ export const agentsHandlers: GatewayRequestHandlers = { const result = pruneAgentConfig(cfg, agentId); await writeConfigFile(result.config); + // Purge session store entries so orphaned sessions cannot be targeted (#65524). + await purgeAgentSessionStoreEntries(cfg, agentId); + if (deleteFiles) { await Promise.all([ moveToTrashBestEffort(workspaceDir),