mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-09 22:54:33 +00:00
fix(gateway): normalize session key casing to prevent ghost sessions (#12846)
* fix(gateway): normalize session key casing to prevent ghost sessions on Linux On case-sensitive filesystems (Linux), mixed-case session keys like agent:ops:MySession and agent:ops:mysession resolve to different store entries, creating ghost duplicates that never converge. Core changes in session-utils.ts: - resolveSessionStoreKey: lowercase all session key components - canonicalizeSpawnedByForAgent: accept cfg, resolve main-alias references via canonicalizeMainSessionAlias after lowercasing - loadSessionEntry: return legacyKey only when it differs from canonicalKey - resolveGatewaySessionStoreTarget: scan store for case-insensitive matches; add optional scanLegacyKeys param to skip disk reads for read-only callers - Export findStoreKeysIgnoreCase for use by write-path consumers - Compare global/unknown sentinels case-insensitively in all canonicalization functions sessions-resolve.ts: - Make resolveSessionKeyFromResolveParams async for inline migration - Check canonical key first (fast path), then fall back to legacy scan - Delete ALL legacy case-variant keys in a single updateSessionStore pass Fixes #12603 * fix(gateway): propagate canonical keys and clean up all case variants on write paths - agent.ts: use canonicalizeSpawnedByForAgent (with cfg) instead of raw toLowerCase; use findStoreKeysIgnoreCase to delete all legacy variants on store write; pass canonicalKey to addChatRun, registerAgentRunContext, resolveSendPolicy, and agentCommand - sessions.ts: replace single-key migration with full case-variant cleanup via findStoreKeysIgnoreCase in patch/reset/delete/compact handlers; add case-insensitive fallback in preview (store already loaded); make sessions.resolve handler async; pass scanLegacyKeys: false in preview - server-node-events.ts: use findStoreKeysIgnoreCase to clean all legacy variants on voice.transcript and agent.request write paths; pass canonicalKey to addChatRun and agentCommand * test(gateway): add session key case-normalization tests Cover the case-insensitive session key canonicalization logic: - resolveSessionStoreKey normalizes mixed-case bare and prefixed keys - resolveSessionStoreKey resolves mixed-case main aliases (MAIN, Main) - resolveGatewaySessionStoreTarget includes legacy mixed-case store keys - resolveGatewaySessionStoreTarget collects all case-variant duplicates - resolveGatewaySessionStoreTarget finds legacy main alias keys with customized mainKey configuration All 5 tests fail before the production changes, pass after. * fix: clean legacy session alias cleanup gaps (openclaw#12846) thanks @mcaxtr --------- Co-authored-by: Peter Steinberger <steipete@gmail.com>
This commit is contained in:
@@ -38,7 +38,12 @@ import {
|
||||
validateAgentParams,
|
||||
validateAgentWaitParams,
|
||||
} from "../protocol/index.js";
|
||||
import { loadSessionEntry } from "../session-utils.js";
|
||||
import {
|
||||
canonicalizeSpawnedByForAgent,
|
||||
loadSessionEntry,
|
||||
pruneLegacyStoreKeys,
|
||||
resolveGatewaySessionStoreTarget,
|
||||
} from "../session-utils.js";
|
||||
import { formatForLog } from "../ws-log.js";
|
||||
import { waitForAgentJob } from "./agent-job.js";
|
||||
import { injectTimestamp, timestampOptsFromConfig } from "./agent-timestamp.js";
|
||||
@@ -213,6 +218,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
let sessionEntry: SessionEntry | undefined;
|
||||
let bestEffortDeliver = false;
|
||||
let cfgForAgent: ReturnType<typeof loadConfig> | undefined;
|
||||
let resolvedSessionKey = requestedSessionKey;
|
||||
|
||||
if (requestedSessionKey) {
|
||||
const { cfg, storePath, entry, canonicalKey } = loadSessionEntry(requestedSessionKey);
|
||||
@@ -220,7 +226,12 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
const now = Date.now();
|
||||
const sessionId = entry?.sessionId ?? randomUUID();
|
||||
const labelValue = request.label?.trim() || entry?.label;
|
||||
spawnedByValue = spawnedByValue || entry?.spawnedBy;
|
||||
const sessionAgent = resolveAgentIdFromSessionKey(canonicalKey);
|
||||
spawnedByValue = canonicalizeSpawnedByForAgent(
|
||||
cfg,
|
||||
sessionAgent,
|
||||
spawnedByValue || entry?.spawnedBy,
|
||||
);
|
||||
let inheritedGroup:
|
||||
| { groupId?: string; groupChannel?: string; groupSpace?: string }
|
||||
| undefined;
|
||||
@@ -268,7 +279,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
const sendPolicy = resolveSendPolicy({
|
||||
cfg,
|
||||
entry,
|
||||
sessionKey: requestedSessionKey,
|
||||
sessionKey: canonicalKey,
|
||||
channel: entry?.channel,
|
||||
chatType: entry?.chatType,
|
||||
});
|
||||
@@ -282,21 +293,32 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
}
|
||||
resolvedSessionId = sessionId;
|
||||
const canonicalSessionKey = canonicalKey;
|
||||
resolvedSessionKey = canonicalSessionKey;
|
||||
const agentId = resolveAgentIdFromSessionKey(canonicalSessionKey);
|
||||
const mainSessionKey = resolveAgentMainSessionKey({ cfg, agentId });
|
||||
if (storePath) {
|
||||
await updateSessionStore(storePath, (store) => {
|
||||
const target = resolveGatewaySessionStoreTarget({
|
||||
cfg,
|
||||
key: requestedSessionKey,
|
||||
store,
|
||||
});
|
||||
pruneLegacyStoreKeys({
|
||||
store,
|
||||
canonicalKey: target.canonicalKey,
|
||||
candidates: target.storeKeys,
|
||||
});
|
||||
store[canonicalSessionKey] = nextEntry;
|
||||
});
|
||||
}
|
||||
if (canonicalSessionKey === mainSessionKey || canonicalSessionKey === "global") {
|
||||
context.addChatRun(idem, {
|
||||
sessionKey: requestedSessionKey,
|
||||
sessionKey: canonicalSessionKey,
|
||||
clientRunId: idem,
|
||||
});
|
||||
bestEffortDeliver = true;
|
||||
}
|
||||
registerAgentRunContext(idem, { sessionKey: requestedSessionKey });
|
||||
registerAgentRunContext(idem, { sessionKey: canonicalSessionKey });
|
||||
}
|
||||
|
||||
const runId = idem;
|
||||
@@ -378,7 +400,7 @@ export const agentHandlers: GatewayRequestHandlers = {
|
||||
images,
|
||||
to: resolvedTo,
|
||||
sessionId: resolvedSessionId,
|
||||
sessionKey: requestedSessionKey,
|
||||
sessionKey: resolvedSessionKey,
|
||||
thinking: request.thinking,
|
||||
deliver,
|
||||
deliveryTargetMode,
|
||||
|
||||
Reference in New Issue
Block a user