From e93ff249b0c2a0ff9b255694db1110c4fde7f911 Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Sat, 2 May 2026 04:56:45 +0100 Subject: [PATCH] fix: preserve manual cli session attachments --- CHANGELOG.md | 1 + src/agents/cli-session.test.ts | 27 +++++++++++++++++++++++++++ src/agents/cli-session.ts | 5 +++++ src/config/sessions/types.ts | 2 ++ 4 files changed, 35 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 1532c55a206..fab0c834e4d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -22,6 +22,7 @@ Docs: https://docs.openclaw.ai ### Fixes - Discord/reactions: skip reaction listener registration when DMs and group DMs are disabled and every configured guild has `reactionNotifications: "off"`, avoiding needless reaction-event queue work. Fixes #47516. Thanks @x4v13r1120. +- CLI sessions: preserve explicit manual-attach reuse bindings so trusted CLI sessions are not invalidated on the first turn when auth, prompt, or MCP fingerprints drift. Fixes #75849. Thanks @alfredjbclaw. - Telegram/streaming: keep partial preview streaming enabled for plain reply-to replies, disabling drafts only for real native quote excerpts that require Telegram quote parameters. Fixes #73505. Thanks @choury. - Config: log the "newer OpenClaw" version warning once per process instead of once per config snapshot read. (#75927) Thanks @romneyda. - Telegram/message actions: treat benign delete-message 400s as no-op warnings instead of runtime errors, so stale or already-removed messages do not create noisy delete failures. Fixes #73726. Thanks @Avicennasis. diff --git a/src/agents/cli-session.test.ts b/src/agents/cli-session.test.ts index c90b63492e9..ffd21c24d53 100644 --- a/src/agents/cli-session.test.ts +++ b/src/agents/cli-session.test.ts @@ -18,6 +18,7 @@ describe("cli-session helpers", () => { setCliSessionBinding(entry, "claude-cli", { sessionId: "cli-session-1", + forceReuse: true, authProfileId: "anthropic:work", authEpoch: "auth-epoch", authEpochVersion: 2, @@ -30,6 +31,7 @@ describe("cli-session helpers", () => { expect(entry.claudeCliSessionId).toBe("cli-session-1"); expect(getCliSessionBinding(entry, "claude-cli")).toEqual({ sessionId: "cli-session-1", + forceReuse: true, authProfileId: "anthropic:work", authEpoch: "auth-epoch", authEpochVersion: 2, @@ -39,6 +41,31 @@ describe("cli-session helpers", () => { }); }); + it("force-reuses explicitly attached CLI sessions despite metadata drift", () => { + const binding = { + sessionId: "cli-session-1", + forceReuse: true, + authProfileId: "anthropic:work", + authEpoch: "auth-epoch-a", + authEpochVersion: 2, + extraSystemPromptHash: "prompt-a", + mcpConfigHash: "mcp-config-a", + mcpResumeHash: "mcp-resume-a", + }; + + expect( + resolveCliSessionReuse({ + binding, + authProfileId: "anthropic:personal", + authEpoch: "auth-epoch-b", + authEpochVersion: 2, + extraSystemPromptHash: "prompt-b", + mcpConfigHash: "mcp-config-b", + mcpResumeHash: "mcp-resume-b", + }), + ).toEqual({ sessionId: "cli-session-1" }); + }); + it("keeps legacy bindings reusable until richer metadata is persisted", () => { const entry: SessionEntry = { sessionId: "openclaw-session", diff --git a/src/agents/cli-session.ts b/src/agents/cli-session.ts index 8f7214c5f8f..0991e94b0b2 100644 --- a/src/agents/cli-session.ts +++ b/src/agents/cli-session.ts @@ -26,6 +26,7 @@ export function getCliSessionBinding( if (bindingSessionId) { return { sessionId: bindingSessionId, + ...(fromBindings?.forceReuse === true ? { forceReuse: true } : {}), authProfileId: normalizeOptionalString(fromBindings?.authProfileId), authEpoch: normalizeOptionalString(fromBindings?.authEpoch), authEpochVersion: fromBindings?.authEpochVersion, @@ -73,6 +74,7 @@ export function setCliSessionBinding( ...entry.cliSessionBindings, [normalized]: { sessionId: trimmed, + ...(binding.forceReuse === true ? { forceReuse: true } : {}), ...(normalizeOptionalString(binding.authProfileId) ? { authProfileId: normalizeOptionalString(binding.authProfileId) } : {}), @@ -139,6 +141,9 @@ export function resolveCliSessionReuse(params: { if (!sessionId) { return {}; } + if (binding?.forceReuse === true) { + return { sessionId }; + } const currentAuthProfileId = normalizeOptionalString(params.authProfileId); const currentAuthEpoch = normalizeOptionalString(params.authEpoch); const currentExtraSystemPromptHash = normalizeOptionalString(params.extraSystemPromptHash); diff --git a/src/config/sessions/types.ts b/src/config/sessions/types.ts index 1873c72dede..3ddb0d66f71 100644 --- a/src/config/sessions/types.ts +++ b/src/config/sessions/types.ts @@ -72,6 +72,8 @@ export type AcpSessionRuntimeOptions = { export type CliSessionBinding = { sessionId: string; + /** Trust an explicitly attached CLI session even when auth, prompt, or MCP fingerprints drift. */ + forceReuse?: boolean; authProfileId?: string; authEpoch?: string; authEpochVersion?: number;