Skip to content

fix(remote): make the host's tab name the one source for CLI and desktop renames - #30037

Merged
nwparker merged 1 commit into
mainfrom
nwparker/remote-tab-rename-host-title
Oct 17, 2026
Merged

nwparker merged 1 commit into
mainfrom
nwparker/remote-tab-rename-host-title

Conversation

@nwparker

@nwparker nwparker commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 4 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​326 0 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​326
Prod 15 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​184 $\color{#cf222e}{\Huge{\mathbf{−}}}$​9 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​175

ELI5

When your desktop is connected to a remote Orca server, the tab name you see and the name orca terminal rename / orca terminal show use on the server could drift apart. A resumed Codex chat made it worse: the desktop showed the chat's saved name and ignored the server's rename. Now the server holds the tab's name, and both sides read and write that one name.

What Changed

  • Before:
    • orca terminal rename on the server returned ok: true and terminal show reported the new name. The desktop tab kept showing the resumed Codex session's saved name.
    • Renaming the tab on the desktop changed only the desktop. terminal show on the server kept the old name.
    • Fresh tabs sometimes looked fine, because nothing outranked the live title. That is why the reporter saw it only after resume.
  • After: a rename from either side shows in the desktop tab, in orca terminal show, and on phones. Clearing a name works in both directions too.
  • Mechanism (root cause): the desktop ranks titles as customTitle > … > saved-session title (aiVaultTitle) > generated > live title (src/shared/tab-title-resolution.ts). For a remote tab, customTitle was only ever this desktop's own copy (buildMirroredTerminalTabs kept existing?.customTitle). The server's rename arrived only as the live title, which sits below the saved-session title. And setTabCustomTitle never told the server anything.
    1. The terminal surfaces in session.tabs.list get an optional customTitle: string | null field: the host's manual name, null when there is none. It is published by headless hosts (mobile-session-terminal-projection.ts, pty-backed-session-publication.ts, and on rename via applyHeadlessSessionTabPropsToSnapshot) and by desktop hosts (mobile-session-terminal-tabs.ts). The live projection passes it through.
    2. The desktop mirror adopts the host's customTitle when the field is present. When it's absent (an older host), the mirror keeps its own name as before.
    3. A desktop rename of a remote tab now goes to the host. It sends terminal.rename by handle when a pane is bound, plus the new optional session.tabs.setTabProps { customTitle } by tab id. The tab-id path reaches panes that are asleep, still starting, or bound to a handle the host has forgotten. On a headless host it renames live panes the same way terminal.rename does. For a paneless tab it persists the name and updates title as well, so phones and older desktops, which read only title, see it too.

Why

There are two possible sources of truth: the desktop or the host. The host is the only one every reader shares (CLI, phones, other desktops), and the execution host owns execution state. Making the desktop authoritative would need a new path to push names to the CLI and phones. Ranking the host's live title above the saved-session title would let any agent OSC title override a resumed chat's name. A separate "manual" field keeps that ranking intact and only promotes names a person actually chose.

Mixed versions: both wire additions are optional. An old host omits customTitle, and the new desktop keeps its own name, as today. An old host's setTabProps schema is non-strict, so it strips the new param and answers updated: true. The terminal.rename the desktop also sends still works against old hosts. Old desktops ignore the new field. One deliberate behavior change: against a new host, a name a desktop only ever kept locally is replaced by the host's (usually none) on the first snapshot after upgrade.

Overlap: #28713 (fork PR, mobile rename, #28665) also adds customTitle to the terminal surface contract, along with much else. This PR uses the same field name and type, so the two can be reconciled, and this PR keeps to the desktop/remote path. Codex review (high) went five rounds. It found three real issues, all fixed with tests: paneless tabs reverting, stale retained handles, and the published title not updating for tab-id renames and resets.

Linked Issue

Fixes #29975

Visual Proof

N/A: no layout change. The tab shows the server's name, and the before/after above is verified as described under Testing. I didn't drive a desktop window against the remote server here.

Testing

  • New orca-runtime-tests/headless-terminal-rename-publication.spec.ts (six cases):

    • CLI rename publishes customTitle on a live headless surface, and clearing it publishes null.
    • Tab-id rename of a paneless tab persists and updates title, and clearing restores the saved title.
    • Tab-id rename and clear of a live pane matches terminal.rename.
    • Resetting a name that was set while the pane slept drops it.
  • New mirrored-terminal-custom-title.test.ts: the host name outranks the saved-session title, the host's null clears the desktop's name, and an older host keeps the desktop's name.

  • New web-runtime-terminal-rename.test.ts: by handle, through a split pane's handle, by tab id, local tabs untouched, disconnected server untouched.

  • The host and mirror tests failed on main. Locally I ran src/renderer/src/runtime, src/renderer/src/store/terminals, src/main/runtime/{session-tabs,workspaces,rpc}, src/shared/rpc-contract and orca-runtime.test.ts, single worker: 7,638 passed. The one failure is preserves existing badgeColor on runtime cloneRepo…, a macOS /private/var tmpdir path mismatch that also fails on main and is unrelated. I also ran pnpm tc, the changed-code quality gate, and the rest of the static-analysis job steps.

  • Live (macOS, isolated orcad built from this branch under /tmp with its own HOME/user-data/XDG, port 6871, no window):

    • orca terminal rename --title 'Codex · 绑定实现' made session.tabs.list publish title and customTitle: "Codex · 绑定实现". --title '' published customTitle: null.
    • session.tabs.setTabProps { tabId, customTitle: "绑定" }, which is what the desktop now sends, made orca terminal show return title: "绑定". That covers the issue's reverse direction.
    • Not verified live: the desktop renderer's display. It is covered by the mirror unit tests.
  • I manually tested these changes locally

  • Automated tests added/updated, or explained why not below

Agent skill upstream boundary

  • Not applicable, or this change copies or mechanically translates no upstream skill-installer source, tests, fixtures, registry entries, path tables, comments, or documentation.

Notes

SSH and folder workspaces: the change is per tab on whichever host runs the terminal, with no worktree assumptions. Mobile: phones read title, which tab-id renames now update. A newer phone could adopt customTitle later.

Checklist

  • This PR is small and focused
  • I explained what changed and why (ELI5, the user-facing before/after, the mechanism, and why over the alternatives)
  • Before/after screenshots or videos attached for UI changes, or N/A with reason
  • Self-reviewed for correctness, security, and performance
  • Cross-platform, SSH/remote, and path/shortcut impact considered (or N/A)
  • pnpm lint, pnpm typecheck, pnpm test, and pnpm build pass (or CI will cover; local preferred)

…top renames

A desktop connected to a remote Orca server kept its own copy of a tab's
manual name, and ranked its saved-session title (a resumed Codex chat)
above the live title the server publishes. So orca terminal rename on the
server never showed on a resumed tab, and a desktop rename never reached
the server.

The server now publishes the tab's manual name as an optional customTitle
on each terminal surface, and the desktop mirror adopts it. Desktop renames
go to the server: terminal.rename by handle, plus session.tabs.setTabProps
customTitle by tab id for panes that are asleep or starting. Older servers
omit customTitle and strip the new param, so the desktop keeps its own
name with them, as before.

Fixes #29975
@coderabbitai

coderabbitai Bot commented Oct 17, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

📝 Walkthrough

The change adds nullable custom-title handling across session-tab contracts, RPCs, runtime snapshots, and terminal surfaces. Headless renames trim titles, update live terminal handles or persist titles when no handles exist, and publish cleared names as null. Desktop rename actions can send remote terminal renames and update web-terminal tab properties. Mirrored tabs apply a host-provided title when present and retain the client title when the host omits the field.

Priority: ⬇️ Low

Merge Risk: 🔵 Low · up to 88f37

Renaming a remote terminal with surrounding whitespace, or clearing its name with only whitespace, can publish the untrimmed value to clients, so the cleared name may not propagate. This is a minor edge case with a simple fix, so the change is mergeable once it is addressed.

Pre-merge checks: Passed 4 · Failed 1

Failed Failed checks: 1 warning

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 42.86% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 14 functions across 19 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
Passed Passed checks: 4 passed
Check name Status Explanation
Title check Passed The title clearly and concisely describes the main change: making the remote host's tab name authoritative for CLI and desktop renames.
Description check Passed The description is complete and relevant. It covers the user impact, mechanism, rationale, compatibility, linked issue, visual-proof exception, testing, platform considerations, and checklist status. …
Linked Issues check Passed Issue [#29975] requires consistent names between the remote CLI and the desktop tab. The change publishes optional nullable customTitle values from host terminal surfaces, adopts host values in the …
Out of Scope Changes check Passed The changed production files and tests support the [#29975] name-synchronization path. They cover host publication, snapshot persistence, RPC forwarding, desktop mirroring, remote rename routing, comp…
Full details: Description check

Explanation

The description is complete and relevant. It covers the user impact, mechanism, rationale, compatibility, linked issue, visual-proof exception, testing, platform considerations, and checklist status. The empty Review section is non-critical.

Full details: Linked Issues check

Explanation

Issue [#29975] requires consistent names between the remote CLI and the desktop tab. The change publishes optional nullable customTitle values from host terminal surfaces, adopts host values in the desktop mirror, sends desktop renames by handle and tab ID, and updates title for paneless tabs. The added host, mirror, and desktop rename tests cover rename and clear operations in both directions. The requested consistency is implemented, so the alternative help-text requirement does not apply.

Full details: Out of Scope Changes check

Explanation

The changed production files and tests support the [#29975] name-synchronization path. They cover host publication, snapshot persistence, RPC forwarding, desktop mirroring, remote rename routing, compatibility with older hosts, and related clear behavior. No demonstrated unrelated production change or unrelated test change is present.

  • Fix all pre-merge checks with AI
✨ Finishing Touches: 💡 1
📝 Generate docstrings: 💡
  • Commit to this branch
  • Create a new PR
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

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: 1


ℹ️ Review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 8d304aca-84e7-4016-859d-aac11945cfeb
📥 Commits

Reviewing files that changed from the base of the PR and between e6b7c77 and 88f37ac.

📒 Files selected for processing: 19
  • src/main/runtime/mobile-session-terminal-projection.ts
  • src/main/runtime/orca-runtime-tests/headless-terminal-rename-publication.spec.ts
  • src/main/runtime/orca-runtime.test.ts
  • src/main/runtime/rpc/methods/session-tab-mutation-methods.ts
  • src/main/runtime/runtime-mobile-session-projection.ts
  • src/main/runtime/session-tabs/headless-tab-properties.ts
  • src/main/runtime/session-tabs/headless-terminal-tab-close.ts
  • src/main/runtime/session-tabs/pty-backed-session-publication.ts
  • src/main/runtime/workspaces/startup/workspace-terminal-provisioning.ts
  • src/main/runtime/workspaces/workspace-removal-target.ts
  • src/renderer/src/runtime/sync-runtime-graph/mobile-session-terminal-tabs.ts
  • src/renderer/src/runtime/web-runtime-terminal-actions.ts
  • src/renderer/src/runtime/web-runtime-terminal-rename.test.ts
  • src/renderer/src/runtime/web-runtime-terminal-rename.ts
  • src/renderer/src/runtime/web-session-tabs-sync/mirrored-terminal-custom-title.test.ts
  • src/renderer/src/runtime/web-session-tabs-sync/terminal-build.ts
  • src/renderer/src/store/terminals/terminal-tab-appearance.ts
  • src/shared/rpc-contract/session-tabs-schemas-params.ts
  • src/shared/runtime-mobile-session-tab-contracts.ts

Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 3 remain after this review.

Comment on lines +316 to +324
this.applyHeadlessSessionTabPropsToSnapshot(worktreeId, hostTabId, {
...args,
...(customTitle !== undefined && liveHandles.length === 0
? {
customTitle,
title: customTitle ?? this.headlessTerminalFallbackTitle(worktreeId, hostTabId)
}
: {})
})

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.

🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Publish the normalized customTitle on the live-pane path, not the raw args.customTitle.

Line 293 normalizes the value: it trims it, and a blank value becomes null. The live-pane path does not use the normalized value.

  • When liveHandles.length > 0, the conditional override on lines 318-323 does not apply.
  • ...args then writes the untrimmed args.customTitle into the snapshot. This overwrites the trimmed value that renameTerminal published.
  • With input " foo ", the host publishes customTitle: " foo ".
  • With a whitespace-only clear such as " ", the host publishes " " instead of null. Clients then never see the name as cleared.

When a notifier with renameTerminal exists, renameTerminal skips its own snapshot write. This spread is then the only thing that publishes customTitle, so the raw value always reaches clients.

Always spread the normalized value. Keep the title override for paneless tabs only.

🐛 Proposed fix
--- "a/src/main/runtime/session-tabs/headless-terminal-tab-close.ts"
+++ "b/src/main/runtime/session-tabs/headless-terminal-tab-close.ts"
@@ -313,15 +313,13 @@
       this.persistHeadlessTerminalTitle(worktreeId, hostTabId, customTitle)
     }
     // A live pane's rename above already set its title; only a paneless tab needs `title` here.
     this.applyHeadlessSessionTabPropsToSnapshot(worktreeId, hostTabId, {
       ...args,
-      ...(customTitle !== undefined && liveHandles.length === 0
-        ? {
-            customTitle,
-            title: customTitle ?? this.headlessTerminalFallbackTitle(worktreeId, hostTabId)
-          }
-        : {})
+      ...(customTitle !== undefined ? { customTitle } : {}),
+      ...(customTitle !== undefined && liveHandles.length === 0
+        ? { title: customTitle ?? this.headlessTerminalFallbackTitle(worktreeId, hostTabId) }
+        : {})
     })
     return { updated: true }
   }
 }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
this.applyHeadlessSessionTabPropsToSnapshot(worktreeId, hostTabId, {
...args,
...(customTitle !== undefined && liveHandles.length === 0
? {
customTitle,
title: customTitle ?? this.headlessTerminalFallbackTitle(worktreeId, hostTabId)
}
: {})
})
this.applyHeadlessSessionTabPropsToSnapshot(worktreeId, hostTabId, {
...args,
...(customTitle !== undefined ? { customTitle } : {}),
...(customTitle !== undefined && liveHandles.length === 0
? { title: customTitle ?? this.headlessTerminalFallbackTitle(worktreeId, hostTabId) }
: {})
})

@nwparker
nwparker merged commit 6743353 into main Oct 17, 2026
44 checks passed
Jinwoo-H added a commit that referenced this pull request Oct 20, 2026
…l rows (#31127)

#30037 made the headless row builder emit customTitle on every terminal row; the Linux baseline
recorded before it (#27688) never listed the field, so the spec fails on main with either layout
switch.

Co-authored-by: E2E Test <e2e@test.local>
brennanb2025 added a commit that referenced this pull request Oct 20, 2026
…cs (#30897)

The daily full E2E run on main fails these specs every time because the
product moved on and PR CI only runs specs a PR touches:
- editor header menu renamed to File actions (#30129)
- terminal attention: an on-screen bell leaves no unread marker (#28637)
- terminal file drops carry their source host (#26830)
- single-choice questions auto-submit (#26288)
- phone terminal rows carry customTitle (#30037)
- gh/git preflight re-reads the login shell PATH (#25183)
- board drag starts after the target column is in view
- chat history anchor read after scroll settles
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.

[Bug]: Remote CLI terminal rename and desktop tab rename are inconsistent

1 participant