Skip to content

fix(terminal): strip OSC/DA query responses from persisted history#1295

Open
murataslan1 wants to merge 2 commits intopingdotgg:mainfrom
murataslan1:fix/terminal-osc-response-filter
Open

fix(terminal): strip OSC/DA query responses from persisted history#1295
murataslan1 wants to merge 2 commits intopingdotgg:mainfrom
murataslan1:fix/terminal-osc-response-filter

Conversation

@murataslan1
Copy link

@murataslan1 murataslan1 commented Mar 22, 2026

Summary

Terminal sessions show gibberish like 10;rgb:f5f5/f5f5/f5f5 after restore.

Root Cause

When the terminal emulator queries colors (OSC 10/11) or device attributes (DA), the terminal responds with escape sequences. These responses are saved into session.history and replayed on restore. Since the xterm.js instance during restore doesn't consume them as query responses, they appear as visible text.

Fix

Strip OSC color report responses (\e]10;...\e\\, \e]11;...\e\\) and DA responses (\e[?1;2c) from data before persisting to history. Live output to the terminal emitter is unchanged so xterm.js can still process them in real-time.

Test plan

  • Start a terminal session, let it run
  • Restart the app or reload the session
  • Verify no escape code gibberish appears in the restored terminal

Fixes #1238

Note

Strip OSC and Device Attributes query responses from persisted terminal history

  • Adds stripTerminalQueryResponses in Manager.ts that removes OSC sequences (ESC ] ... ESC \ or BEL) and DA responses (ESC [ ? digits/semicolons c) via regex.
  • onProcessData sanitizes data before appending to session.history; the raw data is still emitted on the output event.
  • readHistory sanitizes history loaded from disk and rewrites the file if the sanitized content differs from what was stored.

Macroscope summarized c1b2c83.

Terminal color query responses (OSC 10/11 foreground/background reports)
and Device Attributes responses leak into the saved terminal history.
When the session is restored, these escape sequences appear as visible
gibberish like "10;rgb:f5f5/f5f5/f5f5" in the terminal.

Strip these query responses from the data before persisting to history
while still forwarding raw data to the live terminal emitter so
xterm.js can process them correctly.

Fixes pingdotgg#1238
Copilot AI review requested due to automatic review settings March 22, 2026 00:27
@coderabbitai
Copy link

coderabbitai bot commented Mar 22, 2026

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: f68f1e12-dddc-4904-8273-493e8be2f975

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Tip

You can customize the tone of the review comments and chat replies.

Configure the tone_instructions setting to customize the tone of the review comments and chat replies. For example, you can set the tone to Act like a strict teacher, Act like a pirate and more.

@github-actions github-actions bot added size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list. labels Mar 22, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses terminal restore “gibberish” by preventing OSC color report (OSC 10/11) and Device Attributes (DA) query responses from being persisted into session.history, so restored sessions don’t replay those responses as visible text.

Changes:

  • Added a helper to strip terminal query response escape sequences from output before saving to history.
  • Updated onProcessData to append sanitized output to session.history while still emitting the original live data.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

private onProcessData(session: TerminalSessionState, data: string): void {
session.history = capHistory(`${session.history}${data}`, this.historyLineLimit);
const cleanData = stripTerminalQueryResponses(data);
session.history = capHistory(`${session.history}${cleanData}`, this.historyLineLimit);
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

Since sanitization is only applied to new incoming data, any existing persisted history that already contains OSC/DA response gibberish (from older versions) will continue to show up on restore until the user clears history. If the goal is to fix the symptom immediately after upgrade, consider also stripping these sequences when reading history (and rewriting the capped/cleaned history file), or performing a one-time migration/cleanup on open.

Suggested change
session.history = capHistory(`${session.history}${cleanData}`, this.historyLineLimit);
const combinedHistory = stripTerminalQueryResponses(`${session.history}${cleanData}`);
session.history = capHistory(combinedHistory, this.historyLineLimit);

Copilot uses AI. Check for mistakes.
Comment on lines +252 to +258
// OSC responses: \x1b] ... ST where ST is \x1b\\ or \x07
// Examples: \x1b]10;rgb:f5f5/f5f5/f5f5\x1b\\ (foreground color report)
// \x1b]11;rgb:1616/1616/1616\x07 (background color report)
// DA responses: \x1b[ ... c (Device Attributes)
// Example: \x1b[?1;2c
return data
.replace(/\x1b\][^\x07\x1b]*(?:\x1b\\|\x07)/g, "")
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

The OSC-stripping regex currently removes any OSC sequence (ESC ] ... ST/BEL), not just color report responses for OSC 10/11 as described in the PR. This will also drop other OSC features (e.g., OSC 8 hyperlinks, title updates, OSC 7 cwd hints) from restored history, which is a behavior change/regression. Consider narrowing the pattern to only match OSC 10 and 11 responses (and ideally the expected response payload forms) so other OSC sequences remain intact in persisted history.

Suggested change
// OSC responses: \x1b] ... ST where ST is \x1b\\ or \x07
// Examples: \x1b]10;rgb:f5f5/f5f5/f5f5\x1b\\ (foreground color report)
// \x1b]11;rgb:1616/1616/1616\x07 (background color report)
// DA responses: \x1b[ ... c (Device Attributes)
// Example: \x1b[?1;2c
return data
.replace(/\x1b\][^\x07\x1b]*(?:\x1b\\|\x07)/g, "")
// OSC responses (color reports only): \x1b]10;...ST or \x1b]11;...ST
// ST is \x1b\\ or \x07
// Examples: \x1b]10;rgb:f5f5/f5f5/f5f5\x1b\\ (foreground color report)
// \x1b]11;rgb:1616/1616/1616\x07 (background color report)
// DA responses: \x1b[ ... c (Device Attributes)
// Example: \x1b[?1;2c
return data
.replace(/\x1b\](1[01]);[^\x07\x1b]*(?:\x1b\\|\x07)/g, "")

Copilot uses AI. Check for mistakes.
Comment on lines 713 to +715
private onProcessData(session: TerminalSessionState, data: string): void {
session.history = capHistory(`${session.history}${data}`, this.historyLineLimit);
const cleanData = stripTerminalQueryResponses(data);
session.history = capHistory(`${session.history}${cleanData}`, this.historyLineLimit);
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

Stripping is applied per onData chunk (stripTerminalQueryResponses(data)), but PTY data events can split escape sequences across chunks. If an OSC/DA response is split, the first fragment will be persisted (and the terminator fragment won’t match either), so the gibberish can still show up after restore. A more reliable approach is to sanitize on the concatenated stream (e.g., apply stripping to the full history right before persisting / when reading history), or keep a small per-session buffer for incomplete trailing escape sequences between chunks.

Copilot uses AI. Check for mistakes.
Comment on lines +714 to +715
const cleanData = stripTerminalQueryResponses(data);
session.history = capHistory(`${session.history}${cleanData}`, this.historyLineLimit);
Copy link

Copilot AI Mar 22, 2026

Choose a reason for hiding this comment

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

This change introduces new sanitization logic for persisted terminal history, but there doesn’t appear to be a unit test covering it (the repo already has extensive TerminalManagerRuntime history persistence tests). Adding tests that (1) verify OSC 10/11 + DA responses are removed from the persisted history, (2) verify non-target OSC sequences are preserved (if intended), and (3) verify emitted live output events still contain the original data would help prevent regressions.

Copilot uses AI. Check for mistakes.
@nmggithub
Copy link
Contributor

This is a potentially nice sanitization layer, but I'm not sure if this actually fixes the problem or just papers over it. I wonder how OSC/DA query responses are getting into our persisted history in the first place.

Handles sessions saved before this fix — any OSC/DA sequences already
on disk are cleaned on load so they don't appear on restore.
@murataslan1
Copy link
Author

murataslan1 commented Mar 22, 2026

Good question. Here's what's happening:

xterm.js queries the terminal for colors (OSC 10/11) and the PTY responds with sequences like \e]10;rgb:f5f5/...\e\\. These responses come through the same onData callback as regular output, so they end up in session.history. On restore, terminal.write(history) replays everything — but this time xterm.js didn't send the original query, so it doesn't consume the responses and they show up as text.

There's no way to distinguish these from normal output at the transport level since they share the same stream. Stripping before persistence is the right place to handle it. Also added stripping in readHistory() to clean up already-saved sessions.

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

Labels

size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: gibberish rgb escape codes appear in terminal

3 participants