Skip to content

Fix restored scrollback keeping original theme's colors (white-on-white after theme change) - #5175

Merged
austinywang merged 3 commits into
mainfrom
issue-5165-restore-theme-colors
Jun 2, 2026
Merged

austinywang merged 3 commits into
mainfrom
issue-5165-restore-theme-colors

Conversation

@austinywang

@austinywang austinywang commented Jun 2, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Fixes #5165. When cmux restores the previous session's scrollback on launch, restored history kept the default foreground/background baked from the theme it was created under instead of tracking the currently active theme. After a theme change this rendered restored default-colored cells as unreadable white-on-white.

Root cause

Session scrollback is captured via Ghostty's write_screen_file:copy,vt export (TerminalController.readTerminalTextFromVTExportForSnapshot). That export is told to bake the live theme — Surface.writeScreenFile passes .foreground, .background, and .palette to the VT formatter, which prepends OSC 10 / OSC 11 dynamic-color sequences carrying the capture-time default fg/bg (and resolves palette entries to literal RGB).

On restore, cmux replays the captured scrollback by cat-ing it into the freshly launched shell (SessionScrollbackReplayStore → CMUX_RESTORE_SCROLLBACK_FILE → shell integration). Replaying OSC 10/OSC 11 reconfigures the live terminal's dynamic colors, overriding the active theme — so default-colored cells in restored history render with the old theme's default fg/bg (white-on-white when the new theme has a contrasting background).

Empirically confirmed (captured under dark Dracula, restored under a light theme); the snapshot scrollback begins with:

ESC]10;rgb:ff/ff/ff ST   # set foreground = Dracula white
ESC]11;rgb:28/2c/34 ST   # set background = Dracula dark

…and the default-colored restored lines (shell prompt, command output) render invisible (white-on-white) under the light theme.

Fix

Strip terminal-color OSC sequences from the captured scrollback before replay (SessionScrollbackReplayStore). The active theme owns the terminal's default foreground/background/cursor/palette, so restored history must not carry sequences that reconfigure them. Stripped: OSC 4/5/104/105 (palette/special set+reset), 10–19 (dynamic fg/bg/cursor/highlight/…), 110–119 (their resets). Explicit per-cell SGR colors and every non-color escape (titles, OSC 8 hyperlinks, OSC 133 prompt marks, …) are preserved verbatim.

This is done at replay time, so it also repairs scrollback captured by older builds already on disk.

After the fix, restored default-colored cells track the active theme (dark-on-white under a light theme); the white-on-white is gone.

Reproduction / verification

  • Captured a real session snapshot under a dark theme and restored it under a light theme: default-colored lines were invisible (white-on-white). Confirmed the snapshot bakes OSC 10/OSC 11.
  • After the fix, the same restored snapshot renders readable dark-on-white. (Before/after screenshots in the handoff.)

Tests

Two-commit structure per CLAUDE.md:

  1. Failing test — SessionPersistenceTests.testScrollbackReplayStripsThemeBakedDefaultColorOSCSequences asserts the replay path strips terminal-color OSC sequences while preserving SGR colors and non-color escapes. Fails without the fix.
  2. The fix — test passes.

Added to the already-wired cmuxTests/SessionPersistenceTests.swift (XCTest, matching its sibling replay tests). Verified locally with xcodebuild -scheme cmux-unit -only-testing:cmuxTests/SessionPersistenceTests/testScrollbackReplayStripsThemeBakedDefaultColorOSCSequences → ** TEST SUCCEEDED **. E2E/UI left to CI per repo policy.

🤖 Generated with Claude Code


View with Codesmith Autofix with Codesmith
Need help on this PR? Tag @codesmith with what you need. Autofix is disabled.


Note

Low Risk
Localized replay-path text sanitization with a focused regression test; no auth, persistence schema, or network changes.

Overview
Fixes #5165 by sanitizing captured session scrollback before replay so restored default-colored text follows the active theme instead of the theme baked in at capture time.

SessionScrollbackReplayStore now runs scrollback through strippingTerminalColorOSCSequences ahead of truncation and ANSI-safe wrapping. That removes OSC sequences that set or reset terminal colors (palette 4/5/104/105, dynamic fg/bg/cursor 10–19, resets 110–119), including BEL- and ST-terminated forms, while leaving SGR colors and other escapes (e.g. OSC 8 hyperlinks) intact. Because this runs at replay time, existing on-disk snapshots are corrected without re-capture.

Adds regression test testScrollbackReplayStripsThemeBakedDefaultColorOSCSequences in SessionPersistenceTests.

Reviewed by Cursor Bugbot for commit 00a0390. Bugbot is set up for automated code reviews on this repo. Configure here.


Summary by cubic

Fixes #5165 by stripping terminal-color OSC sequences during restored scrollback replay so default-colored text uses the active theme, avoiding white-on-white after theme changes. Done in the replay path so existing snapshots on disk are fixed too.

  • Bug Fixes
    • Strip terminal-color OSC in replay: 4/5/104/105, 10–19, 110–119. Preserve SGR colors and non‑color OSC (e.g., OSC 8 links, titles). Handle BEL and ST (ESC \) terminators; drop truncated sequences.
    • Broadened regression test testScrollbackReplayStripsThemeBakedDefaultColorOSCSequences to cover palette set/reset and dynamic‑color resets, ensuring only theme-reconfiguring OSC are removed while text, SGR, and hyperlinks remain.

Written for commit 00a0390. Summary will update on new commits.

Review in cubic

Summary by CodeRabbit

  • Bug Fixes

    • Scrollback replay now strips embedded terminal color/theme OSC control sequences so replayed output preserves plain text, explicit SGR colors, and hyperlinks without altering the live terminal theme.
  • Tests

    • Added a regression test verifying color/theme OSC sequences are removed while preserving SGR color output, hyperlinks, and plain text.

austinywang and others added 2 commits June 1, 2026 23:00
Session scrollback is captured via Ghostty's `write_screen_file:copy,vt`
export, which prepends terminal-color OSC sequences (OSC 10/11) that bake
the capture-time theme's default foreground/background. Replaying those on
restore reconfigures the freshly launched terminal's dynamic colors, so
restored default-colored cells keep the old theme after a theme change —
rendering as unreadable white-on-white scrollback (#5165).

This regression test asserts that the scrollback replay path strips
terminal-color OSC sequences while preserving explicit SGR colors and
non-color escape sequences (e.g. OSC 8 hyperlinks). It fails until the
fix lands.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
When cmux restores the previous session's scrollback, the captured VT
export bakes the capture-time theme by prepending OSC 10/11 (and the other
dynamic terminal-color OSC sequences). Replaying that into a freshly
launched terminal overrode the active theme's default foreground/background,
so restored default-colored cells kept the original theme. After a theme
change this rendered as unreadable white-on-white scrollback.

Strip terminal-color OSC sequences (palette + dynamic fg/bg/cursor/
highlight colors and their resets: OSC 4/5/10-19/104/105/110-119) from the
captured scrollback before replay, so the active theme owns the default
colors and restored default-colored cells track it. Explicit per-cell SGR
colors and every non-color escape sequence (titles, hyperlinks, prompt
marks) are preserved verbatim.

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

vercel Bot commented Jun 2, 2026 •

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
cmux Ready Ready Preview, Comment Jun 2, 2026 6:44am
cmux-staging Building Building Preview, Comment Jun 2, 2026 6:44am

@coderabbitai

coderabbitai Bot commented Jun 2, 2026 •

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Strips OSC 10/11/12 terminal-color sequences from captured scrollback during normalization and adds a regression test verifying replay output removes those sequences while preserving SGR colors, OSC 8 hyperlinks, and plain text.

Changes

Scrollback Replay Theme Color Stripping

Layer / File(s) Summary
Strip terminal-color OSC sequences
Sources/SessionPersistence.swift
Adds strippingTerminalColorOSCSequences(_:) and isTerminalColorOSCCode(_:), and updates SessionScrollbackReplayStore.normalizedScrollback to remove OSC 10/11/12 terminal-color OSC sequences (terminated by BEL or ESC ) before truncation and replay wrapping.
Scrollback replay OSC sequence stripping test
cmuxTests/SessionPersistenceTests.swift
Adds testScrollbackReplayStripsThemeBakedDefaultColorOSCSequences which constructs scrollback with OSC 10/11/12 theme-default sequences, SGR red, OSC 8 hyperlink, and plain text; runs SessionScrollbackReplayStore.replayEnvironment and asserts theme-baked OSC sequences are removed while preserving explicit SGR and OSC 8 content.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I nibbled bytes of scrollback late at night,
Stripping baked theme colors out of sight,
SGR and links I left to play,
So replayed text will brightly stay,
Hopping onward — tidy, safe, and light.

🚥 Pre-merge checks | ✅ 17 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (17 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main fix: preventing restored scrollback from keeping the original theme's colors, which causes white-on-white rendering after theme changes.
Linked Issues check ✅ Passed The code changes directly address #5165 by stripping terminal-color OSC sequences at replay time, ensuring restored history adapts to the active theme and preventing white-on-white rendering.
Out of Scope Changes check ✅ Passed All changes are scoped to the replay path (SessionScrollbackReplayStore) and testing (SessionPersistenceTests); no unrelated modifications to auth, persistence, or other systems.
Cmux Swift Actor Isolation ✅ Passed Production code adds pure synchronous utility functions using only Sendable value types with no mutable shared state or implicit MainActor issues; test is excluded from the check.
Cmux Swift Blocking Runtime ✅ Passed The PR adds a pure byte-level parser and regression test with no blocking/timing primitives (semaphores, sleeps, locks, polls, DispatchQueue.main.sync) in production code.
Cmux No Hacky Sleeps ✅ Passed PR modifies only Swift files (.swift), which are out of scope; check applies to TypeScript/JavaScript/shell/build scripts only.
Cmux Algorithmic Complexity ✅ Passed O(n) linear-pass algorithm with bounded input (400K chars max). Called once per panel during restore, not in nested scans or batch rescans. Complies with rules.
Cmux Swift Concurrency ✅ Passed PR adds no legacy async patterns. New code is synchronous string parsing and test-only file I/O using FileManager's synchronous APIs, which is explicitly allowed for tests.
Cmux Swift @Concurrent ✅ Passed All Swift changes introduce pure synchronous helper functions (no async/await or @concurrent annotations), which comply with concurrent annotation rules.
Cmux Swift File And Package Boundaries ✅ Passed 98 lines added to existing 2101-line oversized file is below the 250-line threshold. Tightly scoped scrollback replay bug fix with two private functions and clear extraction path.
Cmux Swift Logging ✅ Passed No forbidden logging (print, debugPrint, dump, NSLog) found in Sources/SessionPersistence.swift; test file additions are permitted per swift-logging.md rules.
Cmux User-Facing Error Privacy ✅ Passed PR adds test code and private internal implementation with no user-facing error messages, exposed credentials, or sensitive information violating user-facing-errors.md rules.
Cmux Full Internationalization ✅ Passed The PR adds test code (exempt) and internal production functions for stripping terminal color OSC sequences. No user-facing strings, alerts, UI text, or localization catalog changes are introduced.
Cmux Swiftui State Layout ✅ Passed No SwiftUI changes detected. SessionPersistence.swift and SessionPersistenceTests.swift have no SwiftUI imports or state patterns; check is not applicable.
Cmux Architecture Rethink ✅ Passed Pure functions filter OSC codes before scrollback replay. No timing, dispatch, locks, or shared state. Clear owner and invariant. Qualifies as allowed correctness fix.
Cmux Swift Auxiliary Window Close Shortcuts ✅ Passed PR adds scrollback/OSC stripping logic and test-only fixtures with no NSWindow, NSPanel, NSWindowController, SwiftUI Window, or WindowGroup creation.
Description check ✅ Passed The pull request description is comprehensive and well-structured, covering the bug, root cause, fix details, reproduction/verification, and testing approach.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch issue-5165-restore-theme-colors

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.

@greptile-apps

greptile-apps Bot commented Jun 2, 2026 •

Copy link
Copy Markdown
Contributor

Greptile Summary

Fixes the white-on-white scrollback regression (#5165) by stripping terminal-color OSC sequences (OSC 4/5/10–19/104/105/110–119) from captured scrollback before replay, so restored default-colored cells track the active theme rather than the capture-time theme. The stripping is applied at replay time, so existing on-disk snapshots are corrected without re-capture.

  • Sources/SessionPersistence.swift: Inserts strippingTerminalColorOSCSequences into the normalizedScrollback pipeline before truncation; the byte-level scanner preserves SGR colors, plain text, and non-color OSC sequences (hyperlinks, titles, prompt marks) while dropping only theme-reconfiguring sequences.
  • cmuxTests/SessionPersistenceTests.swift: Adds regression test testScrollbackReplayStripsThemeBakedDefaultColorOSCSequences covering OSC 10/11/12 (dynamic fg/bg/cursor), OSC 4/104 (palette set/reset), OSC 110 (dynamic reset), SGR preservation, and OSC 8 hyperlink preservation.

Confidence Score: 5/5

Safe to merge — the change is a pure additive text filter in the replay pipeline with no side effects on session state, storage, or concurrency.

The OSC stripping logic is linear, handles both BEL and ST terminators, correctly falls through on non-color sequences, and drops only well-defined color-reconfiguring OSC command numbers. The regression test exercises the full round-trip through replayEnvironment, confirming the filter is wired correctly. No actor isolation, blocking primitives, or state-representation issues were introduced.

Sources/SessionPersistence.swift is now over 2100 lines; the new OSC parsing helpers are self-contained and could be extracted, but this does not affect correctness.

Important Files Changed

Filename Overview
Sources/SessionPersistence.swift Adds strippingTerminalColorOSCSequences and isTerminalColorOSCCode to SessionScrollbackReplayStore, stripping OSC 4/5/10-19/104/105/110-119 before replay; logic is correct and linear; file now exceeds 2100 lines
cmuxTests/SessionPersistenceTests.swift Adds regression test covering OSC 10/11/12, OSC 4/104/110 stripping, and preservation of SGR colors, plain text, and OSC 8 hyperlinks

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[normalizedScrollback] --> B{non-nil and non-whitespace?}
    B -- No --> Z[return nil]
    B -- Yes --> C[strippingTerminalColorOSCSequences]
    C --> D{byte is ESC and next is OSC introducer?}
    D -- No --> E[append byte, advance index]
    E --> D
    D -- Yes --> F[parse numeric OSC code]
    F --> G{isTerminalColorOSCCode?}
    G -- No --> H[emit ESC advance by 1 - preserve sequence verbatim]
    H --> D
    G -- Yes --> I[scan for BEL or ST terminator]
    I --> J{terminated?}
    J -- Yes --> K[drop entire OSC sequence]
    J -- No --> L[drop to end of buffer]
    K --> D
    L --> M[return stripped String]
    M --> N[truncatedScrollback]
    N --> O[ansiSafeReplayText]
    O --> P[write replay file]
Loading

Reviews (2): Last reviewed commit: "Broaden restored-scrollback OSC test to ..." | Re-trigger Greptile

Comment on lines +499 to +543
func testScrollbackReplayStripsThemeBakedDefaultColorOSCSequences() {
let tempDir = FileManager.default.temporaryDirectory
.appendingPathComponent("cmux-scrollback-replay-\(UUID().uuidString)", isDirectory: true)
try? FileManager.default.createDirectory(at: tempDir, withIntermediateDirectories: true)
defer { try? FileManager.default.removeItem(at: tempDir) }

let esc = "\u{001B}"
// Captured under a dark theme: default fg baked white, default bg baked dark.
let setForeground = "\(esc)]10;rgb:ff/ff/ff\(esc)\\"
let setBackground = "\(esc)]11;rgb:28/2c/34\(esc)\\"
// A BEL-terminated cursor-color OSC, the other dynamic-color terminator form.
let setCursor = "\(esc)]12;rgb:c0/c1/b5\u{0007}"
let red = "\(esc)[31m"
let reset = "\(esc)[0m"
// OSC 8 hyperlinks are scrollback content, not terminal color config; keep them.
let hyperlink = "\(esc)]8;;https://example.com\(esc)\\link\(esc)]8;;\(esc)\\"
let source = "\(setForeground)\(setBackground)\(setCursor)plain default text\n"
+ "\(red)RED\(reset) \(hyperlink)\n"

let environment = SessionScrollbackReplayStore.replayEnvironment(
for: source,
tempDirectory: tempDir
)

guard let path = environment[SessionScrollbackReplayStore.environmentKey] else {
XCTFail("Expected replay file path")
return
}
guard let contents = try? String(contentsOfFile: path, encoding: .utf8) else {
XCTFail("Expected replay file contents")
return
}

// Terminal-color OSC sequences must be stripped so the active theme owns
// default fg/bg/cursor and restored default cells track it.
XCTAssertFalse(contents.contains("\(esc)]10;"), "OSC 10 (set foreground) must be stripped")
XCTAssertFalse(contents.contains("\(esc)]11;"), "OSC 11 (set background) must be stripped")
XCTAssertFalse(contents.contains("\(esc)]12;"), "OSC 12 (set cursor color) must be stripped")
XCTAssertFalse(contents.contains("rgb:ff/ff/ff"), "baked default-color payload must be gone")

// Explicit SGR colors, plain text, and hyperlinks are preserved verbatim.
XCTAssertTrue(contents.contains("plain default text"))
XCTAssertTrue(contents.contains("\(red)RED\(reset)"))
XCTAssertTrue(contents.contains(hyperlink), "non-color OSC sequences must be preserved")
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P1 Fix commit is missing — bug is still live after merge

CLAUDE.md requires both commits in the PR: "Commit 1: Add the failing test only … Commit 2: Add the fix." This PR contains only commit 1. SessionScrollbackReplayStore.ansiSafeReplayText in Sources/SessionPersistence.swift is unchanged; it only prepends/appends ESC[0m and never strips OSC sequences. As written, the test will fail on every CI run and the white-on-white regression remains unfixed for users who merge this branch.

Comment on lines +534 to +542
XCTAssertFalse(contents.contains("\(esc)]10;"), "OSC 10 (set foreground) must be stripped")
XCTAssertFalse(contents.contains("\(esc)]11;"), "OSC 11 (set background) must be stripped")
XCTAssertFalse(contents.contains("\(esc)]12;"), "OSC 12 (set cursor color) must be stripped")
XCTAssertFalse(contents.contains("rgb:ff/ff/ff"), "baked default-color payload must be gone")

// Explicit SGR colors, plain text, and hyperlinks are preserved verbatim.
XCTAssertTrue(contents.contains("plain default text"))
XCTAssertTrue(contents.contains("\(red)RED\(reset)"))
XCTAssertTrue(contents.contains(hyperlink), "non-color OSC sequences must be preserved")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

P2 Test coverage doesn't match the stated stripping scope

The PR description explicitly commits to stripping OSC 4, 5, 13–19, 104, 105, and 110–119 in addition to 10–12, but the test only asserts on 10, 11, and 12. When the fix lands, none of those additional sequences will be regression-covered. At minimum, representative assertions for the palette group (OSC 4), its reset (OSC 104), and one from the 110–119 reset block should be added so the test actually guards the full claimed scope.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@cmuxTests/SessionPersistenceTests.swift`:
- Around line 505-542: The test only verifies OSC 10/11/12 are stripped; update
the test in SessionPersistenceTests.swift (around the
SessionScrollbackReplayStore.replayEnvironment usage) to include additional OSC
sequences for palette set/reset (OSC 4/5 and their resets 104/105) and
dynamic-color resets (OSC 110–119) in the source string, then add XCTAssertFalse
checks that the replay contents do not contain those OSC prefixes or their
payloads (e.g. "\(esc)]4;", "\(esc)]5;", "\(esc)]104;", "\(esc)]105;", and
"\(esc)]11"‑style 110–119 prefixes and any sample color payloads), while keeping
the existing assertions that SGR colors, plain text, and non-color OSC
hyperlinks are preserved.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d62ac3ef-4417-46ca-8835-81c65cf192f3

📥 Commits

Reviewing files that changed from the base of the PR and between 9eece11 and 5eb10fe.

📒 Files selected for processing (1)
  • cmuxTests/SessionPersistenceTests.swift

Comment thread cmuxTests/SessionPersistenceTests.swift

@cubic-dev-ai cubic-dev-ai Bot left a comment •

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

2 issues found across 1 file

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="cmuxTests/SessionPersistenceTests.swift">

<violation number="1" location="cmuxTests/SessionPersistenceTests.swift:518">
P0: This test calls `SessionScrollbackReplayStore.replayEnvironment(for:tempDirectory:)` and asserts that OSC color sequences are stripped, but the implementation in `SessionScrollbackReplayStore` does not appear to have been modified in this PR to actually perform the stripping. As-is, this test will fail on CI and the white-on-white regression remains unfixed for users.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic

let source = "\(setForeground)\(setBackground)\(setCursor)plain default text\n"
+ "\(red)RED\(reset) \(hyperlink)\n"

let environment = SessionScrollbackReplayStore.replayEnvironment(

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P0: This test calls SessionScrollbackReplayStore.replayEnvironment(for:tempDirectory:) and asserts that OSC color sequences are stripped, but the implementation in SessionScrollbackReplayStore does not appear to have been modified in this PR to actually perform the stripping. As-is, this test will fail on CI and the white-on-white regression remains unfixed for users.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At cmuxTests/SessionPersistenceTests.swift, line 518:

<comment>This test calls `SessionScrollbackReplayStore.replayEnvironment(for:tempDirectory:)` and asserts that OSC color sequences are stripped, but the implementation in `SessionScrollbackReplayStore` does not appear to have been modified in this PR to actually perform the stripping. As-is, this test will fail on CI and the white-on-white regression remains unfixed for users.</comment>

<file context>
@@ -486,6 +486,62 @@ final class SessionPersistenceTests: XCTestCase {
+        let source = "\(setForeground)\(setBackground)\(setCursor)plain default text\n"
+            + "\(red)RED\(reset) \(hyperlink)\n"
+
+        let environment = SessionScrollbackReplayStore.replayEnvironment(
+            for: source,
+            tempDirectory: tempDir
</file context>

Comment thread cmuxTests/SessionPersistenceTests.swift Outdated
@austinywang

Copy link
Copy Markdown
Contributor Author

Regression test verification (local)

The CI tests job is the known-flaky app-host XCTest suite that intermittently hangs on a SwiftUI/WebKit app-host test and is killed at the 900s step timeout, after which scripts/ci/xcodebuild_noninteractive.py + the (0 unexpected) summary check mask it to a pass. On the commit-1 run it hung before reaching SessionPersistenceTests (alphabetically late), so it can't demonstrate the red. I verified the two-commit red/green locally instead:

  • Commit 1 (test only, no fix) — xcodebuild -scheme cmux-unit -only-testing:cmuxTests/SessionPersistenceTests/testScrollbackReplayStripsThemeBakedDefaultColorOSCSequences:
    SessionPersistenceTests.swift:534: XCTAssertFalse failed - OSC 10 (set foreground) must be stripped
    SessionPersistenceTests.swift:535: XCTAssertFalse failed - OSC 11 (set background) must be stripped
    SessionPersistenceTests.swift:536: XCTAssertFalse failed - OSC 12 (set cursor color) must be stripped
    SessionPersistenceTests.swift:537: XCTAssertFalse failed - baked default-color payload must be gone
    ** TEST FAILED **
    
  • Commit 2 (with fix) — same invocation → ** TEST SUCCEEDED **.

So the test genuinely catches the bug. workflow-guard-tests also passes, confirming the test is wired into project.pbxproj (not silently skipped).

Addresses review feedback: the regression test only asserted OSC 10/11/12
were stripped, but the replay-time contract also covers palette set/reset
(OSC 4/5/104/105) and the dynamic-color resets (OSC 110-119). Add
representative coverage — OSC 4 (palette set), OSC 104 (palette reset), and
OSC 110 (foreground reset) — so an implementation that missed those would
fail the test. SGR colors, plain text, and OSC 8 hyperlinks remain asserted
as preserved.

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

Copy link
Copy Markdown
Contributor Author

Thanks for the reviews. Addressed:

  • "Fix commit is missing" (greptile P1 / cubic P0): the bots reviewed the PR when only commit 1 (the failing test) was pushed. The fix is now in commit 5e34af9 (Strip terminal-color OSC sequences from restored scrollback), which adds SessionScrollbackReplayStore.strippingTerminalColorOSCSequences(_:) and calls it from normalizedScrollback. The two-commit red/green is intentional per CLAUDE.md; local verification is in the comment above.
  • "Test coverage doesn't match the full stripping scope" (greptile P2 / coderabbit / cubic P2): broadened the test in commit 00a0390 to also assert OSC 4 (palette set), OSC 104 (palette reset), and OSC 110 (foreground reset) are stripped, alongside the existing OSC 10/11/12, while keeping the SGR-color / plain-text / OSC 8 hyperlink preservation assertions. Re-ran locally → ** TEST SUCCEEDED **.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

♻️ Duplicate comments (1)
cmuxTests/SessionPersistenceTests.swift (1)

511-547: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Expand this regression to the remaining OSC families.

This now covers OSC 4/104/110, but the replay contract for this PR also includes OSC 5/105 and the rest of the 10...19 / 110...119 ranges. An implementation that strips only the currently-sampled opcodes would still pass here.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@cmuxTests/SessionPersistenceTests.swift` around lines 511 - 547, The test
currently asserts stripping for OSC families 4/104/110 only; update the
assertions in SessionPersistenceTests (around the
SessionScrollbackReplayStore.replayEnvironment usage and the local variable
contents) to also assert that OSC 5 and 105 and the full 10...19 and 110...119
ranges are stripped—e.g., add XCTAssertFalse checks for sequences like
"\(esc)]5;", "\(esc)]105;", and "\(esc)]1", "\(esc)]19;", "\(esc)]110;",
"\(esc)]119;" (and any baked payloads such as color/rgb tokens associated with
those ranges) so the replay contract covers all related OSC families rather than
just the sampled opcodes.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Duplicate comments:
In `@cmuxTests/SessionPersistenceTests.swift`:
- Around line 511-547: The test currently asserts stripping for OSC families
4/104/110 only; update the assertions in SessionPersistenceTests (around the
SessionScrollbackReplayStore.replayEnvironment usage and the local variable
contents) to also assert that OSC 5 and 105 and the full 10...19 and 110...119
ranges are stripped—e.g., add XCTAssertFalse checks for sequences like
"\(esc)]5;", "\(esc)]105;", and "\(esc)]1", "\(esc)]19;", "\(esc)]110;",
"\(esc)]119;" (and any baked payloads such as color/rgb tokens associated with
those ranges) so the replay contract covers all related OSC families rather than
just the sampled opcodes.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: d984a266-ef24-4eac-857f-1d5e744be8ab

📥 Commits

Reviewing files that changed from the base of the PR and between 5e34af9 and 00a0390.

📒 Files selected for processing (1)
  • cmuxTests/SessionPersistenceTests.swift

@austinywang
austinywang merged commit 07a8a80 into main Jun 2, 2026
22 checks passed
@austinywang austinywang mentioned this pull request Jun 2, 2026

This branch was successfully deployed

1 active deployment
Preview – cmux — 00a0390b Deployed Jun 2, 2026 by vercel[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Restored session history keeps original theme's colors after theme change (white-on-white scrollback)

1 participant