Skip to content

fix: strip metadata blocks and prevent reply tag streaming leaks#21643

Closed
akramcodez wants to merge 50 commits intoopenclaw:mainfrom
akramcodez:fix/tui-metadata-and-tag-leaks
Closed

fix: strip metadata blocks and prevent reply tag streaming leaks#21643
akramcodez wants to merge 50 commits intoopenclaw:mainfrom
akramcodez:fix/tui-metadata-and-tag-leaks

Conversation

@akramcodez
Copy link
Copy Markdown
Contributor

@akramcodez akramcodez commented Feb 20, 2026

Summary

  • Problem: TUI displayed "Conversation info (untrusted metadata)" JSON blocks on reconnect; [[reply_to_current]] tags leaked during streaming
  • Why it matters: Cluttered chat history with confusing internal messages; broke append-only TUI rendering
  • What changed: Strip metadata blocks via regex in chat-sanitize.ts; prevent streaming lone [ before [[ completes in streaming handler
  • What did NOT change: Non-TUI channels unaffected; existing message sanitization logic preserved

Change Type (select all)

  • Bug fix

Scope (select all touched areas)

  • UI / DX

Linked Issue/PR

User-visible / Behavior Changes

  • Gateway reconnect events no longer render as chat messages in TUI
  • [[reply_to_current]] directives no longer flash/leak during text streaming

Security Impact (required)

  • New permissions/capabilities? No
  • Secrets/tokens handling changed? No
  • New/changed network calls? No
  • Command/tool execution surface changed? No
  • Data access scope changed? No

Repro + Verification

Environment

  • OS: Any
  • Runtime: Node 22+
  • Integration: TUI (openclaw-tui)
  • Config: Default

Steps

  1. Run openclaw-tui
  2. Send messages, then restart gateway (openclaw gateway restart)
  3. Check TUI for metadata blocks

Expected

  • No "Conversation info" blocks visible
  • No stray [ characters during streaming

Actual

  • ✓ Metadata blocks stripped from chat history
  • ✓ Streaming tags don't leak

Evidence

  • Unit tests pass for chat-sanitize.test.ts
  • Unit tests pass for pi-embedded-subscribe.handlers.messages.test.ts

Human Verification (required)

  • Verified scenarios:
    • Regex correctly matches metadata block pattern
    • Trailing [ logic doesn't break closed directives
  • Edge cases checked:
    • Text without metadata (fast-path return)
    • Complete [[...]] tags (not stripped)
  • What you did not verify:
    • Live TUI reconnect behavior (requires manual testing)

Compatibility / Migration

  • Backward compatible? Yes
  • Config/env changes? No
  • Migration needed? No

Greptile Summary

This PR fixes two TUI-specific issues: metadata block leakage and streaming directive tag leaks. The implementation correctly strips "Conversation info (untrusted metadata)" blocks via regex and prevents lone [ characters from appearing before [[reply_to_current]] directives complete during streaming.

Key changes:

  • Added stripInboundMeta() function with regex /Conversation info \(untrusted metadata\):\n```json[\s\S]*?```\n\n/g that matches the actual format generated by buildInboundUserContextPrefix()
  • Integrated stripping into the message sanitization pipeline (applied to both content and text fields)
  • Added logic to strip trailing [ when no [[ is found, preventing partial directive leaks during assistant message streaming
  • Previous review concern addressed: regex pattern corrected to match un-bracketed format

The changes are well-scoped to TUI reconnect scenarios and streaming contexts, with fast-path optimization for messages without metadata.

Confidence Score: 5/5

  • This PR is safe to merge with no concerns
  • The implementation is clean and well-tested. The regex pattern was corrected to match the actual metadata format (previous review concern addressed), tests verify the functionality, and the trailing [ stripping logic is sound since it only applies to assistant messages where [ should never appear outside of complete [[...]] directives. Changes are scoped to sanitization and streaming contexts with no impact on core message handling.
  • No files require special attention

Last reviewed commit: 68940d8

@openclaw-barnacle openclaw-barnacle Bot added gateway Gateway runtime agents Agent runtime and tooling size: XS trusted-contributor labels Feb 20, 2026
Copy link
Copy Markdown
Contributor

@greptile-apps greptile-apps Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

2 files reviewed, 2 comments

Edit Code Review Agent Settings | Greptile

Comment thread src/gateway/chat-sanitize.ts Outdated
Comment thread src/gateway/chat-sanitize.ts Outdated
@akramcodez
Copy link
Copy Markdown
Contributor Author

@greptile-apps please re-review it

@akramcodez
Copy link
Copy Markdown
Contributor Author

Hi @gumadeiras @thewilloftheshadow @cpojer,

Would appreciate a review when you get a chance. This fixes two TUI-specific issues by stripping gateway reconnect metadata blocks from chat history and preventing partial [[reply_to_current]] tags from leaking during streaming.

The changes are scoped to TUI sanitization/streaming only; non-TUI channels and existing message handling remain unaffected.

Thanks!

@vincentkoc
Copy link
Copy Markdown
Member

Status: post-merge follow-up from PR #22142. Keeping this open as a monitored surface for potential metadata / reply-tag streaming gap until validated.

@vincentkoc
Copy link
Copy Markdown
Member

Update: I closed #20807, #21548, and #22147 as redundant/covered by merged #22142.

I am leaving #21643 open for now because it appears to be a distinct TUI streaming-edge fix (chat-sanitize.ts + trailing [ handling) not clearly covered by #22142’s display-boundary stripping. If you want it closed too, I can do that next.

coygeek and others added 15 commits February 20, 2026 17:47
* fix(docker): pin base images to SHA256 digests for supply chain security

Pin all 9 Dockerfiles to immutable SHA256 digests to prevent supply chain
attacks where a compromised upstream image could be silently pulled into
production builds.

Also add Docker ecosystem to Dependabot configuration for automated
digest updates.

Images pinned:
- node:22-bookworm@sha256:cd7bcd2e7a1e6f72052feb023c7f6b722205d3fcab7bbcbd2d1bfdab10b1e935
- node:22-bookworm-slim@sha256:3cfe526ec8dd62013b8843e8e5d4877e297b886e5aace4a59fec25dc20736e45
- debian:bookworm-slim@sha256:98f4b71de414932439ac6ac690d7060df1f27161073c5036a7553723881bffbe
- ubuntu:24.04@sha256:cd1dba651b3080c3686ecf4e3c4220f026b521fb76978881737d24f200828b2b

Fixes openclaw#7731

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

* test(docker): add digest pinning regression coverage

---------

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
…nclaw#21086)

* fix: treat HTTP 503 as failover-eligible for LLM provider errors

When LLM SDKs wrap 503 responses, the leading "503" prefix is lost
(e.g. Google Gemini returns "high demand" / "UNAVAILABLE" without a
numeric prefix). The existing isTransientHttpError only matches
messages starting with "503 ...", so these wrapped errors silently
skip failover — no profile rotation, no model fallback.

This patch closes that gap:

- resolveFailoverReasonFromError: map HTTP status 503 → rate_limit
  (covers structured error objects with a status field)
- ERROR_PATTERNS.overloaded: add /\b503\b/, "service unavailable",
  "high demand" (covers message-only classification when the leading
  status prefix is absent)

Existing isTransientHttpError behavior is unchanged; these additions
are complementary and only fire for errors that previously fell
through unclassified.

* fix: address review feedback — drop /\b503\b/ pattern, add test coverage

- Remove `/\b503\b/` from ERROR_PATTERNS.overloaded to resolve the
  semantic inconsistency noted by reviewers: `isTransientHttpError`
  already handles messages prefixed with "503" (→ "timeout"), so a
  redundant overloaded pattern would classify the same class of errors
  differently depending on message formatting.

- Keep "service unavailable" and "high demand" patterns — these are the
  real gap-fillers for SDK-rewritten messages that lack a numeric prefix.

- Add test case for JSON-wrapped 503 error body containing "overloaded"
  to strengthen coverage.

* fix: unify 503 classification — status 503 → timeout (consistent with isTransientHttpError)

resolveFailoverReasonFromError previously mapped status 503 → "rate_limit",
while the string-based isTransientHttpError mapped "503 ..." → "timeout".

Align both paths: structured {status: 503} now also returns "timeout",
matching the existing transient-error convention. Both reasons are
failover-eligible, so runtime behavior is unchanged.

---------

Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
…0988)

* fix(slack): pass recipient_team_id and recipient_user_id to streaming API calls

The Slack Agents & AI Apps streaming API (chat.startStream / chat.stopStream)
requires recipient_team_id and recipient_user_id parameters. Without them,
stopStream fails with 'missing_recipient_team_id' (all contexts) or
'missing_recipient_user_id' (DM contexts), causing streamed messages to
disappear after generation completes.

This passes:
- team_id (from auth.test at provider startup, stored in monitor context)
- user_id (from the incoming message sender, for DM recipient identification)

through to the ChatStreamer via recipient_team_id and recipient_user_id options.

Fixes openclaw#19839, openclaw#20847, openclaw#20299, openclaw#19791, openclaw#20337

AI-assisted: Written with Claude (Opus 4.6) via OpenClaw. Lightly tested
(unit tests pass, live workspace verification in progress).

* fix(slack): disable block streaming when native streaming is active

When Slack native streaming (`chat.startStream`/`stopStream`) is enabled,
`disableBlockStreaming` was set to `false`, which activated the app-level
block streaming pipeline. This pipeline intercepted agent output, sent it
via block replies, then dropped the final payloads that would have flowed
through `deliverWithStreaming` to the Slack streaming API — resulting in
zero replies delivered.

Set `disableBlockStreaming: true` when native streaming is active so the
final reply flows through the Slack streaming API path as intended.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
…#11046)

- Changed "cask" to "formula" in SKILL.md for consistency.
- Enhanced formula parsing in frontmatter.ts to trim whitespace and fallback to cask if formula is not provided.
…penclaw#21447)

Fixes the pairing required regression from openclaw#21236 for legacy paired devices
created without roles/scopes metadata. Detects legacy paired metadata shape
and skips upgrade enforcement while backfilling metadata in place on reconnect.

Co-authored-by: Josh Avant <830519+joshavant@users.noreply.github.com>
Co-authored-by: Val Alexander <68980965+BunsDev@users.noreply.github.com>
@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

26 similar comments
@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@openclaw-barnacle
Copy link
Copy Markdown

Closing this PR because it looks dirty (too many unrelated or unexpected changes). This usually happens when a branch picks up unrelated commits or a merge went sideways. Please recreate the PR from a clean branch.

@vincentkoc
Copy link
Copy Markdown
Member

Superseded by clean reroute: #22346.
This was re-created as a single clean commit on top of origin/main because the prior head was detached/dirty during rebase cleanup and became stale.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

agents Agent runtime and tooling app: android App: android app: ios App: ios app: macos App: macos app: web-ui App: web-ui channel: bluebubbles Channel integration: bluebubbles channel: discord Channel integration: discord channel: feishu Channel integration: feishu channel: googlechat Channel integration: googlechat channel: imessage Channel integration: imessage channel: irc channel: line Channel integration: line channel: matrix Channel integration: matrix channel: mattermost Channel integration: mattermost channel: msteams Channel integration: msteams channel: nextcloud-talk Channel integration: nextcloud-talk channel: nostr Channel integration: nostr channel: signal Channel integration: signal channel: slack Channel integration: slack channel: telegram Channel integration: telegram channel: tlon Channel integration: tlon channel: twitch Channel integration: twitch channel: voice-call Channel integration: voice-call channel: whatsapp-web Channel integration: whatsapp-web channel: zalo Channel integration: zalo channel: zalouser Channel integration: zalouser cli CLI command changes commands Command implementations docker Docker and sandbox tooling docs Improvements or additions to documentation extensions: copilot-proxy Extension: copilot-proxy extensions: diagnostics-otel Extension: diagnostics-otel extensions: google-antigravity-auth Extension: google-antigravity-auth extensions: google-gemini-cli-auth Extension: google-gemini-cli-auth extensions: llm-task Extension: llm-task extensions: lobster Extension: lobster extensions: memory-core Extension: memory-core extensions: memory-lancedb Extension: memory-lancedb extensions: minimax-portal-auth extensions: open-prose Extension: open-prose gateway Gateway runtime scripts Repository scripts size: XL

Projects

None yet

Development

Successfully merging this pull request may close these issues.

TUI renders gateway reconnect metadata as visible chat messages