Skip to content

perf(screen): session:screen frames are 50% duplicate strings and 99% spinner-only diffs #746

Closed

Description

@Juliusolsson05

Symptom

session:screen is the highest-volume, highest-byte IPC channel and almost all of it is redundant.

Evidence (session recording 794dafdd, 13.2 min, one Claude session): 4,728 frames = 6 Hz, avg 8.3 KB/frame (p99 13.4 KB). Per frame: plain 2,109 B, recent 2,109 B, markdown 1,853 B, recentMarkdown 1,853 B — recent === plain in 4,728/4,728 frames and recentMarkdown === markdown in 4,728/4,728 (an alt-screen TUI has no scrollback). Consecutive frames: 78% differ in exactly one line and 21% in two — the spinner line (✻ Beboppin'… → ✽ Ionizing… → ✶ … (5s · thinking…)). 0% byte-identical, so the renderer's no-op bail never fires. This run: 21,700 frames, 285 MB recorded; one session 8,913 frames = 97 MB.

Mechanism

packages/claude-code-headless/src/terminal/HeadlessTerminal.ts:600-614 gates on exact equality of plain/recent, then computes both markdown walks and emits four strings every 100 ms; ClaudeCodeHeadless.ts:614 forwards every snapshot; src/providers/claude/runtime/claudeSession.ts:490-497 and src/main/sessions/forwarder.ts:57 send them through the 100 ms latest-wins coalescer; useIpcSubscriptions.ts:739-840 commits a new runtime object per frame (chrome-tick frames skip the feed-debug append but still re-render the workspace at 6 Hz × active sessions); SessionRecorderManager JSON-stringifies each frame.

Expected

  • Where recent === plain (and recentMarkdown === markdown), send the field once and let the renderer alias it — halves bytes and removes one markdown walk per frame. Can be done at the src forward point without touching the package.
  • Spinner-aware gate at the forward point: normalise the spinner glyph and the (Ns · …) timer before comparing to the last emitted frame; condition parsers keep running on the raw frame. Renderer: return prev for spinner-only diffs.
  • markdown is debug-only per the comment in useIpcSubscriptions.ts:786-800; consider not sending it unless a debug consumer is mounted.

Impact

~10× fewer screen IPC messages while an agent is thinking, −50% bytes on the rest, half the markdown conversion CPU in main, ~−250 MB per hour of session-recording IO, far fewer workspace re-renders.

Refs #103, #390.

Activity

  1. Juliusolsson05 commented on Sep 5, 2026

    @Juliusolsson05
    OwnerAuthor

    Screen-frame gate #761 now preserves real content edits and seeds late/reloaded local renderers from the raw snapshot cache after successful recovery. Both full host CI gates pass on 2e561bf, with 26 focused gate/recovery/wire tests. Awaiting owner merge confirmation.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions