From 7cd015b20398bf4d2574971bb40a918ae3fe6c5f Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Sun, 5 Apr 2026 14:54:02 +0100 Subject: [PATCH] fix(agents): rotate claude cli bindings on reset --- CHANGELOG.md | 2 ++ src/auto-reply/reply/session.test.ts | 13 +++++++++---- src/auto-reply/reply/session.ts | 14 +++++--------- 3 files changed, 16 insertions(+), 13 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index a9b95c69ec7..8d0b2c03fe0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -173,6 +173,8 @@ Docs: https://docs.openclaw.ai - Voice-call/OpenAI: pass full plugin config into realtime transcription provider resolution so streaming calls can discover the bundled OpenAI realtime transcription provider again. Fixes #60936. Thanks @sliekens and @vincentkoc. - WhatsApp: restore `channels.whatsapp.blockStreaming` and reset watchdog timeouts after reconnect so quiet chats stop falling into reconnect loops. (#60007, #60069) Thanks @MonkeyLeeT and @mcaxtr. - Windows/restart: fall back to the installed Startup-entry launcher when the scheduled task was never registered, so `/restart` can relaunch the gateway on Windows setups where `schtasks` install fell back during onboarding. (#58943) Thanks @imechZhangLY. +- Agents/Claude CLI: persist routed Claude session bindings, rotate them on `/new` and `/reset`, and keep live Claude CLI model switches moving across the configured Claude family so resumed sessions follow the real active thread and model. Thanks @vincentkoc. +- Providers/Anthropic: when Claude CLI auth becomes the default, write a real `claude-cli` auth profile so local and gateway agent runs can use Claude CLI immediately without missing-API-key failures. Thanks @vincentkoc. ## 2026.4.2 diff --git a/src/auto-reply/reply/session.test.ts b/src/auto-reply/reply/session.test.ts index 26de9b96513..a8e675e582f 100644 --- a/src/auto-reply/reply/session.test.ts +++ b/src/auto-reply/reply/session.test.ts @@ -1573,7 +1573,7 @@ describe("initSessionState preserves behavior overrides across /new and /reset", } }); - it("preserves selected auth profile overrides across /new and /reset", async () => { + it("preserves selected auth profile overrides but clears stale cli session bindings across /new and /reset", async () => { const storePath = await createStorePath("openclaw-reset-model-auth-"); const sessionKey = "agent:main:telegram:dm:user-model-auth"; const existingSessionId = "existing-session-model-auth"; @@ -1641,9 +1641,14 @@ describe("initSessionState preserves behavior overrides across /new and /reset", authProfileOverrideSource: overrides.authProfileOverrideSource, authProfileOverrideCompactionCount: overrides.authProfileOverrideCompactionCount, }); - expect(result.sessionEntry.cliSessionIds).toEqual(overrides.cliSessionIds); - expect(result.sessionEntry.cliSessionBindings).toEqual(overrides.cliSessionBindings); - expect(result.sessionEntry.claudeCliSessionId).toBe(overrides.claudeCliSessionId); + expect(result.sessionEntry.cliSessionIds).toBeUndefined(); + expect(result.sessionEntry.cliSessionBindings).toBeUndefined(); + expect(result.sessionEntry.claudeCliSessionId).toBeUndefined(); + + const stored = JSON.parse(await fs.readFile(storePath, "utf-8")); + expect(stored[sessionKey].cliSessionIds).toBeUndefined(); + expect(stored[sessionKey].cliSessionBindings).toBeUndefined(); + expect(stored[sessionKey].claudeCliSessionId).toBeUndefined(); } }); diff --git a/src/auto-reply/reply/session.ts b/src/auto-reply/reply/session.ts index a3771f70d2a..2d3a87510e2 100644 --- a/src/auto-reply/reply/session.ts +++ b/src/auto-reply/reply/session.ts @@ -305,9 +305,6 @@ export async function initSessionState(params: { let persistedAuthProfileOverride: string | undefined; let persistedAuthProfileOverrideSource: SessionEntry["authProfileOverrideSource"]; let persistedAuthProfileOverrideCompactionCount: number | undefined; - let persistedCliSessionIds: SessionEntry["cliSessionIds"]; - let persistedCliSessionBindings: SessionEntry["cliSessionBindings"]; - let persistedClaudeCliSessionId: string | undefined; let persistedLabel: string | undefined; let persistedSpawnedBy: SessionEntry["spawnedBy"]; let persistedSpawnedWorkspaceDir: SessionEntry["spawnedWorkspaceDir"]; @@ -501,9 +498,8 @@ export async function initSessionState(params: { persistedAuthProfileOverride = entry.authProfileOverride; persistedAuthProfileOverrideSource = entry.authProfileOverrideSource; persistedAuthProfileOverrideCompactionCount = entry.authProfileOverrideCompactionCount; - persistedCliSessionIds = entry.cliSessionIds; - persistedCliSessionBindings = entry.cliSessionBindings; - persistedClaudeCliSessionId = entry.claudeCliSessionId; + // Explicit /new and /reset should rotate the underlying CLI conversation too. + // Keep the model/auth choice, but force the next turn to mint a fresh CLI binding. persistedLabel = entry.label; persistedSpawnedBy = entry.spawnedBy; persistedSpawnedWorkspaceDir = entry.spawnedWorkspaceDir; @@ -573,9 +569,9 @@ export async function initSessionState(params: { persistedAuthProfileOverrideSource ?? baseEntry?.authProfileOverrideSource, authProfileOverrideCompactionCount: persistedAuthProfileOverrideCompactionCount ?? baseEntry?.authProfileOverrideCompactionCount, - cliSessionIds: persistedCliSessionIds ?? baseEntry?.cliSessionIds, - cliSessionBindings: persistedCliSessionBindings ?? baseEntry?.cliSessionBindings, - claudeCliSessionId: persistedClaudeCliSessionId ?? baseEntry?.claudeCliSessionId, + cliSessionIds: baseEntry?.cliSessionIds, + cliSessionBindings: baseEntry?.cliSessionBindings, + claudeCliSessionId: baseEntry?.claudeCliSessionId, label: persistedLabel ?? baseEntry?.label, spawnedBy: persistedSpawnedBy ?? baseEntry?.spawnedBy, spawnedWorkspaceDir: persistedSpawnedWorkspaceDir ?? baseEntry?.spawnedWorkspaceDir,