#Something interesting with the <session-recap> block injection in v2026.3.8.

1 messages · Page 1 of 1 (latest)

sharp shuttle
#

Observed behaviour:

Control UI: The <session-recap> block is visibly rendered as part of the user message when using a Gemini model. Switching to Claude hides it correctly.
TUI shell: Always renders the <session-recap> block visibly, regardless of model.
Expected behaviour: The block should be silent in both surfaces — it's agent context, not user-visible chat content.

Questions:

Is there a config flag to suppress the visible rendering? Or is this a known client-side rendering gap?
Is the model-dependent behaviour in the Control UI intentional, or a rendering quirk based on how Gemini's stream format differs from Claude's?
Running v2026.3.8 on macOS. Happy to share screenshots or session logs if useful.

next epochBOT
#

To help others find answers, you can mark your question as solved via Right click solution message -> Apps -> âś… Mark Solution

hollow sluiceBOT
#

No config flag for this (at least nothing in the config/docs), and the behavior you’re seeing lines up with a rendering / transcript-hygiene gap, not something intentional.

What’s going on (based on current code paths):

  • The Control UI (and other UIs) only “hide” a small set of internal scaffolding:
    • assistant-side: <think> / <thinking> / (and “thinking” content blocks), plus <relevant-memories>…</relevant-memories> via stripAssistantInternalScaffolding() (src/shared/text/assistant-visible-text.ts)
    • user-side: it strips the injected “Conversation info / Sender info / …” blocks via stripInboundMetadata() (src/auto-reply/reply/strip-inbound-meta.ts)
  • There is no stripper for <session-recap>…</session-recap> in either path, so if that tag shows up in stored message text, the UI will render it literally.
  • The model-dependent difference in the Control UI is very plausibly because Claude tends to deliver “hidden stuff” as structured thinking blocks (which the UI can omit), while Gemini ends up with that content in plain text (or as a synthetic user turn / injected text), so it leaks.

TUI always showing it also makes sense: it’s essentially rendering the stored transcript text, and since <session-recap> isn’t in the current “internal scaffolding” strip list, it won’t get suppressed.

What I’d do next:

  1. Check if this is happening with /verbose off and /reasoning off (those can cause extra internal-ish output to appear, per the FAQ: https://docs.openclaw.ai/help/faq#how-do-i-stop-internal-system-messages-from-showing-in-chat)

  2. If it still happens (likely), it’s issue-worthy. The most useful debug info would be:

#
  • a screenshot (or pasted snippet) showing whether <session-recap> appears inside a user bubble or assistant bubble
  • the exact Gemini model id/provider you’re using
  • openclaw status --all (safe to share; redacts tokens)

If you drop those, we can point to the exact spot to fix (it’ll likely be “treat <session-recap> as internal scaffolding and strip it”, and/or “don’t persist synthetic recap turns as user-visible messages” in the transcript/UI pipeline).

sharp shuttle
#

Hey @krill — follow-up with the details you asked for.

<session-recap> appears in the user bubble (not assistant), as it's prepended to the outgoing message before it hits the gateway.

Confirmed behaviour:

Control UI + Gemini (google/gemini-3.1-flash-lite-preview): <session-recap> block visibly rendered in the user message bubble (screenshot attached)
Control UI + Claude (anthropic/claude-sonnet-4-6): hidden correctly
TUI: always renders visibly regardless of model (screenshot attached)
openclaw status --all:

Version: 2026.3.8 | macOS 15.7.3 (arm64) | Node 25.6.1 Agents: main (active), minimax Channels: Telegram âś“, WhatsApp âś“, Discord off Default model: anthropic/claude-sonnet-4-6 Test model: google/gemini-3.1-flash-lite-preview
Looks like <session-recap> just needs to be added to the stripInboundMetadata() strip list alongside the existing scaffolding tags. Happy to test a fix if you push one.

hollow sluiceBOT
#

Yep — with that extra detail, I’m convinced this is a UI/transcript hygiene bug, not an intentional “Gemini streams differently” thing.

What your repro implies

  • Because the <session-recap> blob is prepended into the stored user message text, it needs to be stripped in the same place we already strip inbound “Conversation info / Sender info / …” blocks.
  • Today stripInboundMetadata() only knows about the sentinel-style JSON fences + the trailing “Untrusted context …” suffix. It does not strip arbitrary XML-ish scaffolding blocks like <session-recap>…</session-recap>.
  • That also explains:
    • Control UI + Claude: no recap-prepend path, so nothing to show
    • Control UI + Gemini: recap-prepend path active, so it leaks
    • TUI: renders stored user text → always leaks

Likely fix (small + safe)

Add a “strip leading <session-recap> block” step to:

  • src/auto-reply/reply/strip-inbound-meta.ts (stripInboundMetadata, and probably stripLeadingInboundMetadata too)

Implementation detail I’d recommend (to avoid eating legit user content):

  • only strip it if the first non-whitespace content starts with <session-recap (and maybe support _ variant too)
#
  • strip through the matching close tag; if the close tag is missing, drop the remainder (similar to the existing “unfinished relevant-memories” behavior on the assistant side)

And add a unit test in:

  • src/auto-reply/reply/strip-inbound-meta.test.ts

Next step

Since I can’t push a fix from here, the best path is:

  • open a GitHub issue with your repro + screenshots + the model ids you included
  • optionally add the patch + test as a PR

GitHub issues: https://github.com/openclaw/openclaw/issues

If you paste a short snippet of the exact prefix shape you’re seeing (just the first ~10–20 lines, redacted), I can suggest an exact regex/stripper that matches it tightly.

sharp shuttle
#

snippet...

<session-recap> <summary>Found 10 recent items across 3 categories</summary> <ledger-items count="2"> <item path="ledger/2026-03-09.md" modified="2026-03-09 10:49"> <title>ledger/2026-03-09.md</title> <snippet># Observation Ledger — 2026-03-09...</snippet> </item> </ledger-items> <handoff-items count="6"> <item path="handoffs/handoff-2026-03-09-1012.md" modified="2026-03-09 10:12"> <title>handoff-2026-03-09-1012</title> <snippet># Session Handoff...</snippet> </item> </handoff-items> <task-items count="2"> <item path="tasks/..." status="open"> ... </item> </task-items> </session-recap>

river epoch
#

Had the same issue. Try npx clawaid — it auto-diagnoses and fixes common gateway/config problems. Saved me a lot of debugging time.

hollow sluiceBOT
#

Perfect, that snippet is exactly what we needed.

Given that the recap is in the user-role message content, the right place to hide it is indeed the display stripper for user messages:

  • src/auto-reply/reply/strip-inbound-meta.ts → stripInboundMetadata() (and probably stripLeadingInboundMetadata() too)

Two important notes:

  1. This is display-only, so stripping it there won’t remove it from what gets sent to the model (good — it stays as agent context), it just stops it leaking into UI/TUI renders.

  2. You’ll need to adjust the “fast path” too: right now stripInboundMetadata() immediately returns the original string if none of the JSON sentinel headers are present — which is why your <session-recap> prefix slips through.

Suggested minimal patch shape

Add a quick check + a “strip leading recap block” pass before the existing sentinel parsing:

#
// strip-inbound-meta.ts
const SESSION_RECAP_QUICK_RE = /<\s*session[-_]recap\b/i;
const SESSION_RECAP_OPEN_LINE_RE = /^<\s*session[-_]recap\b[^<>]*>\s*$/i;
const SESSION_RECAP_CLOSE_LINE_RE = /^<\s*\/\s*session[-_]recap\s*>\s*$/i;

function stripLeadingSessionRecapBlock(lines: string[]): string[] {
  let i = 0;
  while (i < lines.length && lines[i]?.trim() === "") i++;
  if (i >= lines.length) return lines;

  if (!SESSION_RECAP_OPEN_LINE_RE.test(lines[i] ?? "")) return lines;

  // Skip until close tag (or EOF)
  i++;
  while (i < lines.length && !SESSION_RECAP_CLOSE_LINE_RE.test(lines[i] ?? "")) i++;
#
  if (i < lines.length) i++; // consume close tag

  // Drop blank separator lines after recap
  while (i < lines.length && lines[i]?.trim() === "") i++;
  return lines.slice(i);
}

export function stripInboundMetadata(text: string): string {
  if (!text || (!SENTINEL_FAST_RE.test(text) && !SESSION_RECAP_QUICK_RE.test(text))) {
    return text;
  }

  let lines = text.split("\n");
  lines = stripLeadingSessionRecapBlock(lines);
#
  // then continue with the existing sentinel-strip logic using `lines`…
}

Unit test to add

In src/auto-reply/reply/strip-inbound-meta.test.ts:

  • it("strips leading <session-recap> block", …)
  • and one guard like it("does not strip <session-recap> when it appears mid-message", …) (so we only treat it as internal when it’s prefixed).

Filing

If you open the GitHub issue, include that snippet + “user bubble prefix” detail, and point maintainers at strip-inbound-meta.ts fast-path. That should make it a quick fix.

If you end up making a PR, I’m happy to sanity-check the approach here (especially the “only strip when it’s the first non-empty line” guard, which avoids nuking legit user text).

#

I don’t recognize clawaid as an official OpenClaw tool/CLI, so I wouldn’t recommend running npx clawaid (supply-chain risk + not something we can support/debug here).

For OpenClaw, the supported diagnostics are:

  • openclaw status --all
  • openclaw doctor
  • openclaw logs --follow

Docs: https://docs.openclaw.ai/gateway/troubleshooting

Also: this specific <session-recap> leak looks like a UI rendering / transcript-stripping bug, not a gateway/config health issue, so “auto-fix config” tools wouldn’t really address it anyway. The right fix is adding <session-recap> prefix stripping in stripInboundMetadata() (and a unit test) + filing it upstream on GitHub.

sharp shuttle
hollow sluiceBOT
#

Nice — thanks for filing it and linking the issue.

If you want to make it extra easy for someone to pick up, the key details to highlight (which you already basically found) are:

  • it’s user-role text, so the fix belongs in stripInboundMetadata() (and the fast-path needs to consider session-recap too)
  • add a unit test in src/auto-reply/reply/strip-inbound-meta.test.ts to prevent regressions

If this thread’s essentially resolved now (tracked upstream), please mark the solution message in the forum so Answer Overflow indexes it.