mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-14 01:46:38 +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:
@@ -1,5 +1,5 @@
|
||||
import type { OpenClawConfig } from "../config/config.js";
|
||||
import { loadSessionStore } from "../config/sessions.js";
|
||||
import { loadSessionStore, updateSessionStore } from "../config/sessions.js";
|
||||
import { parseSessionLabel } from "../sessions/session-label.js";
|
||||
import {
|
||||
ErrorCodes,
|
||||
@@ -10,15 +10,16 @@ import {
|
||||
import {
|
||||
listSessionsFromStore,
|
||||
loadCombinedSessionStoreForGateway,
|
||||
pruneLegacyStoreKeys,
|
||||
resolveGatewaySessionStoreTarget,
|
||||
} from "./session-utils.js";
|
||||
|
||||
export type SessionsResolveResult = { ok: true; key: string } | { ok: false; error: ErrorShape };
|
||||
|
||||
export function resolveSessionKeyFromResolveParams(params: {
|
||||
export async function resolveSessionKeyFromResolveParams(params: {
|
||||
cfg: OpenClawConfig;
|
||||
p: SessionsResolveParams;
|
||||
}): SessionsResolveResult {
|
||||
}): Promise<SessionsResolveResult> {
|
||||
const { cfg, p } = params;
|
||||
|
||||
const key = typeof p.key === "string" ? p.key.trim() : "";
|
||||
@@ -46,13 +47,25 @@ export function resolveSessionKeyFromResolveParams(params: {
|
||||
if (hasKey) {
|
||||
const target = resolveGatewaySessionStoreTarget({ cfg, key });
|
||||
const store = loadSessionStore(target.storePath);
|
||||
const existingKey = target.storeKeys.find((candidate) => store[candidate]);
|
||||
if (!existingKey) {
|
||||
if (store[target.canonicalKey]) {
|
||||
return { ok: true, key: target.canonicalKey };
|
||||
}
|
||||
const legacyKey = target.storeKeys.find((candidate) => store[candidate]);
|
||||
if (!legacyKey) {
|
||||
return {
|
||||
ok: false,
|
||||
error: errorShape(ErrorCodes.INVALID_REQUEST, `No session found: ${key}`),
|
||||
};
|
||||
}
|
||||
await updateSessionStore(target.storePath, (s) => {
|
||||
const liveTarget = resolveGatewaySessionStoreTarget({ cfg, key, store: s });
|
||||
const canonicalKey = liveTarget.canonicalKey;
|
||||
// Migrate the first legacy entry to the canonical key.
|
||||
if (!s[canonicalKey] && s[legacyKey]) {
|
||||
s[canonicalKey] = s[legacyKey];
|
||||
}
|
||||
pruneLegacyStoreKeys({ store: s, canonicalKey, candidates: liveTarget.storeKeys });
|
||||
});
|
||||
return { ok: true, key: target.canonicalKey };
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user