Skip to content

[Bug]: Session key inconsistency causes duplicate sessions and missing messages in TUI/WebUI #9561

@hillghost86

Description

@hillghost86

name: Bug report
about: Report a problem or unexpected behavior in Clawdbot.
title: "[Bug]: Session key inconsistency causes duplicate sessions and missing messages in TUI/WebUI"
labels: bug

Summary

There is an inconsistency in how recordInboundSession and initSessionState handle sessionKey, causing duplicate session entries to be created when plugins pass mixed-case session keys. This results in user messages being written to the wrong transcript file and not appearing in TUI/WebUI, even though AI replies are correctly generated and sent.

Steps to reproduce

  1. Create a channel plugin that calls recordInboundSession with a mixed-case sessionKey (e.g., agent:main:wechat:miniprogram:onslD1wi_zoYBJggvREAPv-Dtl8E)
  2. Send a message through the plugin
  3. Check sessions.json - you will see multiple session entries for the same user (one with original case, one with lowercase)
  4. Check TUI/WebUI - user messages are not displayed, but AI replies are visible

Expected behavior

  • Only one session entry should be created per user
  • User messages should appear in TUI/WebUI alongside AI replies
  • Session keys should be consistently normalized (lowercase) throughout the codebase

Actual behavior

  • Multiple session entries are created for the same user (differentiated by case)
  • User messages are written to a transcript file that TUI/WebUI doesn't display
  • AI replies work correctly but user messages are missing from the UI

Environment

  • Clawdbot version: 2026.2.3 (5d82c82)
  • OS: macOS 26.2 (arm64)
  • Install method: npm/pnpm

Root Cause Analysis

The Problem

  1. recordInboundSession (in src/channels/session.ts) uses the provided sessionKey parameter directly without normalization:

    export async function recordInboundSession(params: {
      storePath: string;
      sessionKey: string;  // Uses the provided sessionKey directly, no normalization
      ctx: MsgContext;
      // ...
    }): Promise<void> {
      const { storePath, sessionKey, ctx, groupResolution, createIfMissing } = params;
      void recordSessionMetaFromInbound({
        storePath,
        sessionKey,  // Used directly, does not call resolveSessionKey
        ctx,
        // ...
      }).catch(params.onRecordError);
    }
  2. initSessionState (in src/auto-reply/reply/session.ts) calls resolveSessionKey which normalizes the key to lowercase:

    sessionKey = resolveSessionKey(sessionScope, sessionCtxForState, mainKey);
  3. resolveSessionKey (in src/config/sessions/session-key.ts) converts ctx.SessionKey to lowercase:

    export function resolveSessionKey(scope: SessionScope, ctx: MsgContext, mainKey?: string) {
      const explicit = ctx.SessionKey?.trim();
      if (explicit) {
        return explicit.toLowerCase();  // ⚠️ Converts to lowercase
      }
      // Otherwise regenerates based on scope and ctx.From
      const raw = deriveSessionKey(scope, ctx);
      // ...
    }

Impact

  • If a plugin passes agent:main:wechat:miniprogram:onslD1wi_zoYBJggvREAPv-Dtl8E (mixed case)
  • recordInboundSession creates a session entry with this key
  • initSessionState converts it to agent:main:wechat:miniprogram:onsld1wi_zoybjggvreapv-dtl8e (lowercase)
  • Result: Two different session entries, messages split between them

Proposed Fixes

Solution 1: Normalize sessionKey in recordInboundSession

Modify src/channels/session.ts:

import { resolveSessionKey } from "../config/sessions/session-key.js";

export async function recordInboundSession(params: {
  storePath: string;
  sessionKey: string;
  ctx: MsgContext;
  // ...
}): Promise<void> {
  const { storePath, sessionKey, ctx, groupResolution, createIfMissing } = params;
  
  // Normalize sessionKey to ensure consistency with initSessionState
  const normalizedSessionKey = ctx.SessionKey?.trim() 
    ? ctx.SessionKey.trim().toLowerCase()
    : sessionKey.toLowerCase();
  
  void recordSessionMetaFromInbound({
    storePath,
    sessionKey: normalizedSessionKey,  // Use normalized key
    ctx,
    groupResolution,
    createIfMissing,
  }).catch(params.onRecordError);
  // ...
}

Solution 2: Ensure plugins always pass lowercase SessionKey

In plugin code, ensure SessionKey is always lowercase (workaround, not a fix):

const msgContext = runtime.channel.reply.finalizeInboundContext({
  // ...
  SessionKey: sessionKey.toLowerCase(),  // Ensure lowercase
  // ...
});

Solution 3: Normalize in recordSessionMetaFromInbound (Recommended)

Modify src/config/sessions/store.ts:

export async function recordSessionMetaFromInbound(params: {
  storePath: string;
  sessionKey: string;
  ctx: MsgContext;
  // ...
}): Promise<SessionEntry | null> {
  const { storePath, sessionKey, ctx } = params;
  const createIfMissing = params.createIfMissing ?? true;
  
  // Normalize sessionKey (convert to lowercase)
  const normalizedKey = sessionKey.toLowerCase();
  
  return await updateSessionStore(storePath, (store) => {
    const existing = store[normalizedKey];  // Use normalized key
    // ...
    store[normalizedKey] = next;  // Use normalized key
    return next;
  });
}

Solution 3 is recommended because:

  1. Normalizes at the data storage layer, ensuring all session keys are lowercase
  2. No need to modify all call sites of recordInboundSession
  3. Consistent with resolveSessionKey behavior (both convert to lowercase)

Affected Files

  • src/channels/session.ts - recordInboundSession function
  • src/config/sessions/store.ts - recordSessionMetaFromInbound function
  • src/config/sessions/session-key.ts - resolveSessionKey function
  • src/auto-reply/reply/session.ts - initSessionState function

Logs or screenshots

Example log output showing the issue:

2026-02-05T10:00:54.429Z info gateway/channels/wechat-miniprogram
Route resolved: agentId=main, sessionKey=agent:main:main, mainSessionKey=agent:main:main

2026-02-05T10:00:54.430Z info gateway/channels/wechat-miniprogram
Recording inbound session: sessionKey=agent:main:wechat:miniprogram:onslD1wi_zoYBJggvREAPv-Dtl8E

Notice the mismatch: resolveAgentRoute returns agent:main:main, but recordInboundSession is called with agent:main:wechat:miniprogram:onslD1wi_zoYBJggvREAPv-Dtl8E.

Multiple sessions in openclaw status:

Sessions
┌─────────────────────────────────────────────┬────────┬─────────┬──────────────┬────────────────┐
│ Key                                         │ Kind   │ Age     │ Model        │ Tokens         │
├─────────────────────────────────────────────┼────────┼─────────┼──────────────┼────────────────┤
│ agent:main:wechat:miniprogram:o…            │ direct │ 2m ago  │ glm-4.7       │ 14k/205k (7%)  │
│ agent:main:main                             │ direct │ 2m ago  │ glm-4.7       │ 26k/205k (13%) │
│ agent:main:wechat:miniprogram:o…            │ direct │ 2m ago  │ glm-4.7       │ 0.0k/205k (0%) │
└─────────────────────────────────────────────┴────────┴─────────┴──────────────┴────────────────┘

Workaround

Manually delete duplicate session entries from ~/.openclaw/agents/main/sessions/sessions.json. After cleanup, TUI/WebUI will display correctly.

Testing Recommendations

  1. Call recordInboundSession with a mixed-case sessionKey
  2. Verify only one session entry is created in sessions.json (lowercase)
  3. Verify user messages are written to the correct transcript file
  4. Verify TUI/WebUI correctly displays messages

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingstaleMarked as stale due to inactivitytuiTerminal UI changes

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions