Skip to content

Keep Claude session titles on terminal tabs#6971

Open
brennanb2025 wants to merge 3 commits into
mainfrom
brennanb2025/sync-terminal-titles
Open

Keep Claude session titles on terminal tabs#6971
brennanb2025 wants to merge 3 commits into
mainfrom
brennanb2025/sync-terminal-titles

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

This makes terminal tab labels follow Claude/iTerm2-style OSC title behavior automatically: OSC 0/1 are treated as authoritative tab titles, while OSC 2 is kept as a legacy fallback for panes that have not observed OSC 0/1. Manual custom tab titles still win.

Boundary: this does not turn OSC 2 into the primary visible tab title protocol. OSC 2 continues to update raw runtime/window-title state and only supplies visible labels for legacy producers until a pane observes OSC 0/1.

Discord context: https://discord.com/channels/1492228674081263786/1517463258641076256
Linear: STA-728

Design approach

  • Matched iTerm2's practical split: OSC 0/1 describe tab/icon title, OSC 2 describes window title.
  • Added a reducer for terminal tab title precedence so local, restored, split-pane, runtime, web/mobile session, and SSH paths use the same rule.
  • Preserved raw OSC 0/1/2 observations for runtime/status surfaces while separately caching the accepted visible tab title per pane.

Design review outcome

  • Scratch design doc: /tmp/orca-sync-terminal-titles-design.md.
  • Delegated design review completed clean after iterating the doc around precedence, SSH behavior, restored sessions, split panes, and validation coverage.

Implementation and deviations

  • Implemented shared title extraction and precedence in terminal-tab-title-reducer and propagated titleSource through runtime/session schemas.
  • Local and remote PTY transports now observe OSC titles without letting OSC 2 overwrite an authoritative OSC 0/1 tab title.
  • Pane-visible title sync is pane-local, so split panes do not block each other's OSC 2 fallback behavior.
  • Restored authoritative titles seed reducer state; fresh replacement PTYs clear stale accepted titles.
  • Also fixed a CodexRestartChip render loop discovered during Electron validation. That fix is included because it blocked validating this UI path.
  • No material deviations from the reviewed design.

Completeness verification

  • Delegated completeness verification passed against Brennan's request, the design doc, and product surfaces.
  • Headline behavior status: implemented.

Broad app consistency

  • Source-of-truth behavior compared: iTerm2/terminal OSC semantics, with OSC 0/1 as tab/icon title and OSC 2 as window title.
  • Neighboring Orca surfaces checked: terminal tab labels, unified tab labels, split panes, manual custom tab titles, restored terminal tabs, local PTYs, remote runtime/SSH PTYs, web/mobile session snapshots, session persistence, runtime graph sync, and terminal sorting/title epochs.
  • Existing mismatch fixed: Ghostty-style lack of automatic Claude session tab title adoption is no longer the Orca behavior for OSC 0/1 producers.
  • Preserved mismatch intentionally: OSC 2-only producers still get a legacy fallback so older shells/apps are not made worse.

Risks, ambiguities, and non-goals

  • Risk: some shells emit prompt titles with OSC 0. The reducer follows terminal semantics, so those titles can become authoritative for that pane.
  • Non-goal: changing the Electron/native OS window title.
  • Non-goal: parsing non-OSC title protocols or guessing titles from plain terminal text.
  • Non-goal: making Ghostty behave differently; the ticket records that Ghostty does not do this, but this PR changes Orca.

Performance audit

  • Delegated perf audit covered OSC scanning hot paths, store subscribers, session write churn, runtime graph cleanup, and remote reducer lifetime.
  • Fixes folded in: avoid OSC scanning on ordinary ANSI escape floods, ignore decorative spinner-frame-only updates for accepted pane titles, treat missing source and legacy fallback equivalently for session writes, and clean remote reducer state on terminal end/disconnect/stale handle.
  • Deterministic unit coverage was added/updated for the touched hot paths; no remaining accepted perf gaps.

Code review loop

  • Delegated review loop ran multiple rounds with paired reviewers.
  • Findings fixed included split-pane tab-level authority leaks, restored title seeding, fresh PTY replacement resets, and web-session label-source equality.
  • Final reviewers returned clean.

Brennan's PR process validation

  • Local Electron validation passed on this exact branch/worktree with isolated profile.
  • Verified OSC 1 + OSC 2: visible label stayed Claude Visible Title with authoritative-tab, while raw runtime pane title showed Shell Window Title.
  • Verified OSC 2-only fallback: visible label became Legacy Fallback Title with legacy-window-fallback.
  • Verified manual custom title wins over later OSC 1.
  • SSH validation passed through a throwaway Linux Docker SSH target and visible remote terminal UI.
  • SSH authoritative case: visible remote tab stayed OSC1_UI_REMOTE; raw pane title showed OSC2_UI_IGNORED.
  • SSH fallback case: fresh remote pane accepted OSC2_ONLY_REMOTE_NO_PROMPT as legacy-window-fallback.

Screenshot evidence

  • Local OSC 1 authoritative: /tmp/orca-title-validation-run/screenshots/osc1-authoritative-not-overwritten.png
  • Local OSC 2 fallback: /tmp/orca-title-validation-run/screenshots/osc2-legacy-fallback.png
  • Manual custom title wins: /tmp/orca-title-validation-run/screenshots/manual-custom-title-wins.png
  • SSH OSC 1 authoritative: /tmp/orca-title-ssh-validation-evidence-1782868350/ssh-osc1-authoritative.png
  • SSH OSC 2 fallback: /tmp/orca-title-ssh-validation-evidence-1782868350/ssh-osc2-legacy-fallback.png
  • SSH evidence log: /tmp/orca-title-ssh-validation-evidence-1782868350/remote-and-relay-evidence.txt

Static checks and focused tests

  • pnpm exec vitest run --config config/vitest.config.ts focused title/runtime batches: latest reported 20 files / 695 tests passed.
  • Focused post-fix batch: pty-connection.test.ts and web-session-tabs-sync.test.ts, 224 tests passed.
  • Reviewer verification batches: 12 files / 397 tests passed, and additional 11-file / 390-test plus 6-file / 292-test batches passed.
  • pnpm build:relay passed during SSH validation.
  • pnpm run typecheck passed.
  • pnpm run lint passed.
  • git diff --check passed.

Post-PR stabilization

  • Completed. Initial CI passed except the branch needed a rebase; after rebasing onto main, CI passed again.
  • CodeRabbit posted two actionable comments: one empty-title hibernation cleanup issue and one OSC fallback precedence comment request. Both were fixed, validated locally, pushed, and the review threads are resolved.
  • Final PR checks are passing: verify and wayland terminal input.
  • PR is open and unmerged.

Made with Orca 🐋


Open in Stage

@coderabbitai

coderabbitai Bot commented Jul 1, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

📝 Walkthrough

Walkthrough

This change introduces title provenance tracking for terminal tabs, distinguishing "authoritative" titles derived from OSC 1 escape sequences versus "legacy" fallback titles from OSC 2/window titles. OSC parsing now returns structured title updates with a target (icon/window/both), consumed by a new reducer that determines acceptance precedence. This flows through PTY transports, pane connection logic, and a new accepted-pane-title store map, with cleanup wired into tab/worktree/project removal and hibernation. Session hydration, persistence, and runtime/mobile sync were updated to carry and respect this provenance. A separate, unrelated change refactors CodexRestartChip's store selector usage and collapse-state synchronization, adding a render test.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.74% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title is specific and aligned with the main change: terminal tab title behavior.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description check ✅ Passed The description covers summary, testing, screenshots, review, security, and notes, with enough detail despite not using the template headings verbatim.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 2

🧹 Nitpick comments (8)
src/renderer/src/components/CodexRestartChip.tsx (1)

97-101: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document why collapse state is synchronized from noticeKey.

This effect/current-state split is carrying a non-obvious invariant: avoid render-time state updates while still re-expanding on notice changes. A short comment here would make the render-loop fix much harder to accidentally undo.

Suggested note
   const [collapseState, setCollapseState] = useState(() =>
     createCodexRestartOverlayCollapseState(noticeKey)
   )
+  // Keep notice-key resets out of render so new notices re-expand cleanly
+  // without reintroducing render-time state updates.
   useEffect(() => {
     setCollapseState((prev) => getCodexRestartOverlayCollapseState(prev, noticeKey))
   }, [noticeKey])

As per coding guidelines, **/*.{ts,tsx,js,jsx} changes driven by a non-obvious constraint should include a short comment explaining why the code behaves that way.

Source: Coding guidelines

src/renderer/src/components/codex-restart-chip-render.test.tsx (1)

31-59: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a regression case that exercises the new noticeKey path.

This test only covers the restartNotice === undefined branch, so it never hits the new useEffect(...[noticeKey]) sync or the collapse-reset behavior. A second case that renders with a notice, collapses it, then swaps to a different notice would actually guard the render-loop fix.

src/renderer/src/store/slices/tabs-hydration.ts (1)

76-98: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a short why-comment for the titleSourcelabelSource backfill.

This lookup through session.tabsByWorktree and tab.entityId is migration-only behavior, so the intent is hard to infer during future refactors. A 1-2 line note that this preserves terminal title provenance for older unified sessions would make the contract much safer to maintain.

As per coding guidelines, “When writing or modifying code driven by a design doc or non-obvious constraint, add a comment explaining why the code behaves the way it does.”

Source: Coding guidelines

src/renderer/src/lib/session-write-subscriber.ts (1)

34-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Document the legacy-source normalization.

Treating undefined as 'legacy-window-fallback' is the key persistence invariant here, but that’s not obvious from the code alone. A short why-comment near the normalization would make it clear that source-only migrations must persist while decorative title-frame churn stays ignored.

As per coding guidelines, “When writing or modifying code driven by a design doc or non-obvious constraint, add a comment explaining why the code behaves the way it does.”

Also applies to: 84-93

Source: Coding guidelines

src/shared/terminal-tab-title-reducer.test.ts (1)

7-27: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Cover the authoritative-first path too.

This only exercises blank-title handling. The reducer's main contract is that a later window update stops being accepted after the first icon/both title, and that branch is still untested here.

src/shared/tab-title-resolution.ts (1)

4-46: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a comment explaining the authoritative-title precedence rule.

Inserting authoritativeTitle/authoritativeLabel between customTitle/customLabel and quickCommandLabel is a core, non-obvious design decision of this PR (OSC 0/1 titles outrank quick-command and generated labels, but not a manual custom title; OSC 2 legacy titles rank below both). A short inline comment would help future readers understand why authoritative titles sit at this specific point in the chain rather than, e.g., before customTitle or after generatedTitle.

📝 Suggested comment
+  // Why: OSC 0/1 (authoritative) titles should override quick-command/generated
+  // fallback labels but still yield to an explicit manual custom title. OSC 2
+  // (legacy) titles fall through to the same priority as the raw live title.
   const authoritativeTitle = resolveAuthoritativeTitle(tab.title, tab.titleSource)

As per coding guidelines, **/*.{ts,tsx,js,jsx}: "When writing or modifying code driven by a design doc or non-obvious constraint, add a comment explaining why the code behaves the way it does."

Source: Coding guidelines

src/renderer/src/runtime/web-session-tabs-sync.ts (1)

498-506: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Centralize the authoritative-only titleSource rule.

This same “emit provenance only for authoritative visible titles” decision is implemented here and again in src/renderer/src/runtime/sync-runtime-graph.ts. Keeping it inline in both paths makes the mobile publisher and the web-session applier easy to drift. Please extract a shared helper for this rule, or at minimum add a one-line why comment here if you want to keep it local. As per coding guidelines, “When writing or modifying code driven by a design doc or non-obvious constraint, add a comment explaining why the code behaves the way it does.”

Source: Coding guidelines

src/renderer/src/components/terminal-pane/pane-visible-tab-title-sync.ts (1)

13-31: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add a short why-comment for the precedence rule.

This helper is now the shared boundary for the non-obvious “accepted pane title wins; raw runtime title is only a legacy fallback” contract, but that rationale is only implicit in the code. A 1-2 line comment here would make future changes much safer.

Suggested edit
 export function resolvePaneVisibleTabTitle(
   state: PaneVisibleTitleState,
   tabId: string,
   paneId: number
 ): PaneVisibleTabTitle | null {
+  // Why: accepted pane titles already passed the OSC 0/1 vs OSC 2 precedence
+  // reducer, so only fall back to raw runtime titles when no accepted title exists.
   const acceptedTitle = state.acceptedPaneTabTitlesByTabId[tabId]?.[paneId]
   if (acceptedTitle?.title.trim()) {
     return acceptedTitle
   }

As per coding guidelines, "When writing or modifying code driven by a design doc or non-obvious constraint, add a comment explaining why the code behaves the way it does."

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 0de162be-70a0-408a-836e-5bdc6255f984

📥 Commits

Reviewing files that changed from the base of the PR and between faa0994 and e6b6c3f.

📒 Files selected for processing (44)
  • src/renderer/src/components/CodexRestartChip.tsx
  • src/renderer/src/components/codex-restart-chip-render.test.tsx
  • src/renderer/src/components/terminal-pane/TerminalPane.tsx
  • src/renderer/src/components/terminal-pane/pane-visible-tab-title-sync.test.ts
  • src/renderer/src/components/terminal-pane/pane-visible-tab-title-sync.ts
  • src/renderer/src/components/terminal-pane/pty-connection-types.ts
  • src/renderer/src/components/terminal-pane/pty-connection.test.ts
  • src/renderer/src/components/terminal-pane/pty-connection.ts
  • src/renderer/src/components/terminal-pane/pty-dispatcher.ts
  • src/renderer/src/components/terminal-pane/pty-transport.test.ts
  • src/renderer/src/components/terminal-pane/pty-transport.ts
  • src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.test.ts
  • src/renderer/src/components/terminal-pane/remote-runtime-pty-transport.ts
  • src/renderer/src/components/terminal-pane/use-terminal-pane-lifecycle.ts
  • src/renderer/src/hooks/useIpcEvents.ts
  • src/renderer/src/lib/session-write-subscriber.test.ts
  • src/renderer/src/lib/session-write-subscriber.ts
  • src/renderer/src/runtime/sync-runtime-graph-key-reuse.test.ts
  • src/renderer/src/runtime/sync-runtime-graph.test.ts
  • src/renderer/src/runtime/sync-runtime-graph.ts
  • src/renderer/src/runtime/web-session-tabs-sync.test.ts
  • src/renderer/src/runtime/web-session-tabs-sync.ts
  • src/renderer/src/store/slices/repos.ts
  • src/renderer/src/store/slices/runtime-pane-title-sort-epoch.test.ts
  • src/renderer/src/store/slices/store-cascades.test.ts
  • src/renderer/src/store/slices/tabs-hydration.ts
  • src/renderer/src/store/slices/tabs.ts
  • src/renderer/src/store/slices/terminal-helpers.test.ts
  • src/renderer/src/store/slices/terminal-helpers.ts
  • src/renderer/src/store/slices/terminal-orphan-helpers.ts
  • src/renderer/src/store/slices/terminals-hydration.test.ts
  • src/renderer/src/store/slices/terminals.ts
  • src/renderer/src/store/slices/worktrees.test.ts
  • src/renderer/src/store/slices/worktrees.ts
  • src/shared/agent-detection.test.ts
  • src/shared/agent-detection.ts
  • src/shared/osc-title-extraction.ts
  • src/shared/runtime-types.ts
  • src/shared/tab-title-resolution.test.ts
  • src/shared/tab-title-resolution.ts
  • src/shared/terminal-tab-title-reducer.test.ts
  • src/shared/terminal-tab-title-reducer.ts
  • src/shared/types.ts
  • src/shared/workspace-session-schema.ts

Comment thread src/renderer/src/store/slices/terminals.ts Outdated
Comment thread src/shared/terminal-tab-title-reducer.ts
Co-authored-by: Orca <help@stably.ai>
@brennanb2025 brennanb2025 force-pushed the brennanb2025/sync-terminal-titles branch from e6b6c3f to cb9f7d7 Compare July 1, 2026 01:50
brennanb2025 and others added 2 commits June 30, 2026 19:00
Co-authored-by: Orca <help@stably.ai>
Co-authored-by: Orca <help@stably.ai>
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.

1 participant