Repository navigation
Give native chat names one source for tabs, sidebar and AI Vault (list and search) - #25986
Conversation
…ir copies The host's saved conversation name now rides the structured session status feed, which already exists per host, is keyed by the durable session id, and keeps a closed chat's summary. Tab strip, sidebar rows and AI Vault (list and search) read it through one hook and one display order (tab alias, saved name, host label). Vault no longer copies names into its cached results, so the projection, recovery and pending-title modules and their tab-snapshot lanes are removed. Indexed search hits now carry the native owner and saved name from the host that indexed them.
Status: ready for review (not merged)What this fixes: AI Vault could flash a native chat's name back to "Claude Chat"/"Codex Chat" when its tab closed, and Vault search never showed chat names or manual tab renames. The name now comes from one place (the host's per-chat status summary), read the same way by the tab strip, the sidebar row, and Vault's list and search. About 720 lines of Vault-only name-repair code are deleted (production code on this path: +381 / −1059). Checked on head
Not verified: a live paired remote host or an older paired host at runtime (covered by unit tests only), Windows and Linux runs, and generation by a real model (a stand-in naming command was used). Known limits, labelled in the PR body: hosts that predate the new optional field show the host's list title, refreshed when Vault refreshes, with no repeated probing (temporary until version data says those hosts can be dropped). Search still does not match on generated names; that is a separate feature. |
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configuration
📒 Files selected for processing (5)
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review. 📝 WalkthroughWalkthroughStructured-session hosts now publish saved conversation names through status feeds. AI Vault search results can include structured-session ownership and projected names. Renderer views read names from the owning host and apply custom-label or session-title fallbacks. The change also removes snapshot-based title publication, renderer title recovery and projection code, and related request fields. Priority: ➖ Normal Merge Risk: 🔵 Low · up to Search responses that need no ownership data may still wait for chat-host setup. The change is mergeable with awareness of that bounded delay. 🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches 💡 1📝 Generate docstrings 💡
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. Comment |
…-name-feed # Conflicts: # src/main/native-chat/agent-session-wire/structured-agent-session-status-feed.ts
Naming and owning search hits is bookkeeping: if the native chat host fails to install, return the plain hits instead of failing the search. The runtime RPC only installs the host for clients that will receive the owners.
A failed feed publication no longer skips retitling the open tab. The publish now lives in the naming deps, where a test covers it.
Drop a published chat name the record store would refuse, and cap a search hit's owner workspace id at the same length the list row and record use.
A paired host's search hits carry no resume command, so the row disabled every open action even for a native chat it can open through its owner, as its list row does.
There was a problem hiding this comment.
Actionable comments posted: 1
ℹ️ Review info
⚙️ Run configuration
- Configuration used: Repository UI
- Review profile: CHILL
- Plan: Advanced
- Run ID:
e3c9fae2-b979-46be-a42a-6f5a6a1f5fbb
📒 Files selected for processing (19)
config/scripts/vitest-node-runtime-files.mjssrc/main/ai-vault/structured-session-ownership.tssrc/main/ipc/ai-vault-search.test.tssrc/main/ipc/ai-vault-search.tssrc/main/native-chat/agent-session-wire/structured-agent-session-client-delivery.tssrc/main/native-chat/agent-session-wire/structured-agent-session-host.tssrc/main/native-chat/agent-session-wire/structured-agent-session-status-feed.tssrc/main/runtime/orca-runtime-get-worktree-ps.tssrc/main/runtime/orca-runtime-prune-mobile-session-tab-group-layout.tssrc/main/runtime/rpc/methods/ai-vault-structured-ownership.test.tssrc/main/runtime/rpc/methods/ai-vault.tssrc/main/runtime/structured-chat-naming-runtime.test.tssrc/main/runtime/structured-chat-naming-runtime.tssrc/renderer/src/components/right-sidebar/AiVaultSessionRow.structured-title.test.tsxsrc/renderer/src/components/right-sidebar/AiVaultVirtualRow.test.tsxsrc/renderer/src/components/right-sidebar/ai-vault-search-session.test.tssrc/renderer/src/components/right-sidebar/ai-vault-search-session.tssrc/renderer/src/runtime/structured-conversation-name.tssrc/shared/ai-vault-search-contract.ts
Included review availability: This review used your included allowance. Your plan provides up to 10 included reviews per hour; 7 remain after this review.
| ) | ||
| : true | ||
| const response = await search | ||
| return (await hostReady) ? projectStructuredAiVaultSearchResponse(response) : response |
There was a problem hiding this comment.
🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win
🔎 Supported by static analysis
🏁 Script executed:
sed -n '35,115p' src/main/ai-vault/structured-session-ownership.ts
sed -n '125,180p' src/main/ipc/ai-vault-search.ts
sed -n '22,50p' src/main/runtime/rpc/methods/ai-vault.tsRepository: stablyai/orca
Length of output: 6199
🏁 Script executed:
printf '%s\n' '--- helper/call references ---'
rg -n -F -- 'ensureStructuredAgentSessionHostUnlessRefused' src/main
rg -n -F -- 'searchWithStructuredOwners' src
rg -n -F -- 'ensureStructuredSessionOwnership' src/main
printf '%s\n' '--- response type and consumer references ---'
rg -n -F -- 'AiVaultSearchResponse' src/main shared
printf '%s\n' '--- scoped PR diff ---'
git diff --unified=12 f6448a1e26c0bb0f17998bb5e8865de7bfd4949e 9807f0472604f62472a458a3e29f4674471372a8 -- src/main/ai-vault/structured-session-ownership.ts src/main/ipc/ai-vault-search.ts src/main/runtime/rpc/methods/ai-vault.tsRepository: stablyai/orca
Length of output: 20818
🏁 Script executed:
printf '%s\n' '--- host-refusal helper ---'
sed -n '1,85p' src/main/runtime/structured-agent-session-host-refusal.ts
printf '%s\n' '--- host ensure declarations and bindings ---'
rg -n -F -- 'ensureStructuredAgentSessionHost' src/main
printf '%s\n' '--- RPC search caller contract ---'
sed -n '1,60p' src/main/runtime/rpc/methods/ai-vault.ts
printf '%s\n' '--- search response contract ---'
sed -n '1,180p' src/shared/ai-vault-search-types.ts
printf '%s\n' '--- local search registration and invocation ---'
sed -n '210,244p' src/main/ipc/register-core-handlers/register-core-handlers.ts
sed -n '135,170p' src/main/ipc/ai-vault-search.ts
printf '%s\n' '--- local/all-host search response handling ---'
sed -n '1,160p' src/main/ipc/ai-vault-search-all-hosts.tsRepository: stablyai/orca
Length of output: 38985
🏁 Script executed:
printf '%s\n' '--- bound runtime method ---'
nl -ba src/main/runtime/orca-runtime-get-worktree-ps.ts | sed -n '150,184p'
printf '%s\n' '--- host installer implementation ---'
nl -ba src/main/runtime/structured-agent-session-runtime.ts | sed -n '145,215p'
printf '%s\n' '--- response kinds and schema ---'
rg -n -C 5 -- 'stale-cursor|unavailable|AiVaultSearchResponseSchema' src/shared/ai-vault-search-contract.ts src/main/ai-vault-search/session-search-service-registry.ts src/main/ai-vault-search/session-search-instance.ts
printf '%s\n' '--- search service registry ---'
nl -ba src/main/ai-vault-search/session-search-service-registry.ts | sed -n '1,100p'
printf '%s\n' '--- search instance cursor handling ---'
nl -ba src/main/ai-vault-search/session-search-instance.ts | sed -n '70,130p']]>Repository: stablyai/orca
Length of output: 20545
🏁 Script executed:
printf '%s\n' '--- bound runtime method ---'
nl -ba src/main/runtime/orca-runtime-get-worktree-ps.ts | sed -n '150,184p'
printf '%s\n' '--- host installer implementation ---'
nl -ba src/main/runtime/structured-agent-session-runtime.ts | sed -n '145,215p'
printf '%s\n' '--- response kinds and schema ---'
rg -n -C 5 -- 'stale-cursor|unavailable|AiVaultSearchResponseSchema' src/shared/ai-vault-search-contract.ts src/main/ai-vault-search/session-search-service-registry.ts src/main/ai-vault-search/session-search-instance.ts
printf '%s\n' '--- search service registry ---'
nl -ba src/main/ai-vault-search/session-search-service-registry.ts | sed -n '1,100p'
printf '%s\n' '--- search instance cursor handling ---'
nl -ba src/main/ai-vault-search/session-search-instance.ts | sed -n '70,130p'Repository: stablyai/orca
Length of output: 22437
🏁 Script executed:
printf '%s\n' '--- complete runtime setup method ---'
nl -ba src/main/runtime/orca-runtime-get-worktree-ps.ts | sed -n '168,225p'
printf '%s\n' '--- installer location ---'
rg -n -- '^(async )?function install\\(' src/main/runtime/structured-agent-session-runtime.ts
printf '%s\n' '--- installer body ---'
nl -ba src/main/runtime/structured-agent-session-runtime.ts | sed -n '1,145p'Repository: stablyai/orca
Length of output: 13765
Return non-result and empty searches before awaiting host setup.
searchWithStructuredOwners waits for hostReady even when the response is unavailable, stale-cursor, or has no hits. These responses need no ownership projection, so this wait can delay the search response until lazy host installation completes. Return them first; the setup rejection handler remains attached.
Suggested fix
const response = await search
+ if (response.kind !== 'results' || response.hits.length === 0) {
+ return response
+ }
return (await hostReady) ? projectStructuredAiVaultSearchResponse(response) : response📝 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.
| return (await hostReady) ? projectStructuredAiVaultSearchResponse(response) : response | |
| if (response.kind !== 'results' || response.hits.length === 0) { | |
| return response | |
| } | |
| return (await hostReady) ? projectStructuredAiVaultSearchResponse(response) : response |
A paired host's row or search hit could carry a workspace id such as "constructor", which read an Object.prototype member as a tab list and broke the render. The shared tab index now reads only own workspace entries.
Review and QA summary (head
|
| Item | What | Commit |
|---|---|---|
| Merge | Merged main and resolved one conflict in the status feed (kept both sides) | 7805a2a7383 |
| Search depended on the chat host | If the native chat host fails to start, Vault search now returns plain results instead of failing. Clients that can't open native chats no longer wait for it | e761f169163 |
| Naming publish | The status feed and the open tab's title are updated independently, so one failing can't skip the other. Test added for the real naming path | 7962c5dc9a6 |
| Unchecked remote input | A name from a paired host is checked with the same rule the record store uses, and the search hit's workspace id limit now matches the list row's (512) | 3c4f9f04abc |
| Paired search hits couldn't open | Hits from a paired host carried the chat owner but had no resume command, so the row disabled opening. They now open the native chat | 9807f047260 |
| Crash from a malformed paired host | A workspace id such as constructor made a Vault row throw while rendering. Tab lookups now read only real entries |
0946766240c |
| Comment | Noted why the status feed must keep closed chats' entries: Vault names depend on it | 4a3431e8394 |
Also checked and not a problem: a name saved on a live chat counts as a status change and re-runs that change's handlers. Each one is already guarded, so there is no second naming run, duplicate mail, second workspace rename or extra notification.
Deferred (P2 and below, not blocking)
- Pre-existing native chats stay "Claude Chat"/"Codex Chat". Naming only runs on a chat's first message, so chats created before Name native Claude and Codex chats after their first message #25724 never get a name. The Vault list already shows them generically on main; this PR extends that to search and
orca search, where the first-prompt text used to tell them apart. This is an open product decision. - Short flash on Vault reopen. If a chat is named while the Vault panel is hidden and no tab from that host is open, reopening Vault shows the cached "Codex Chat" until the status feed reconnects. That is about one local round trip, longer for a paired host. Fixing it without a renderer copy of the name means keeping the local status feed always on; left as a follow-up.
- Offline paired hosts are retried. Vault rows from a paired host keep that host's status connection open while they are on screen, including retries every few seconds when it is offline. An open tab from that host already does the same, and it stops when the rows unmount.
- Release timing. This PR removes a field Name native Claude and Codex chats after their first message #25724 added to tab snapshots. That is safe only because Name native Claude and Codex chats after their first message #25724 has not shipped: no release tag contains it, including the v1.4.222 draft. This should land before the next release cut that includes Name native Claude and Codex chats after their first message #25724. The body's "latest is v1.4.221" is now out of date.
- Minor: while a chat host keeps failing to install, each (debounced) search logs a warning. The tab's drag label still uses the tab label. A rename on a live chat only notifies from the one naming call site.
Verified
- Review: an adversarial review of the whole PR (host side, renderer side, deleted code and tests, plus an independent design comparison), two follow-up reviews of the fixes, and a release readiness pass (remote and cross-platform, crash/security/performance, compatibility). No P0 or P1 was found. The design comparison reached the same design independently and recommends keeping it as one PR. Splitting would bring back the flash on close.
- Performance: measured with a script. With 2,000 chats in the feed and 400 rows subscribed, one status event costs about 0.5 ms, and only rows whose name changed re-render.
- Electron QA: hidden app on a separate Mac, with stand-in Claude/Codex CLIs and a stand-in naming command, at head
9807f047260. The final head differs only by the tab-lookup guard and was not re-run. All six scenarios passed:- Closed right after naming (Codex and Claude): the same Vault row keeps the name, with no refresh.

- Closed while naming was still running: the row shows "Codex Chat", then the name by itself.

- A manual rename wins in the tab, sidebar, Vault list and search, and clearing it brings the generated name back.

- Search names a closed chat, and the hit's Resume button opens the native chat.

- Names survive an app restart.

- Paired host: the chat's name shows in the client's Vault list and search, and the hit opens the chat.

- Closed right after naming (Codex and Claude): the same Vault row keeps the name, with no refresh.
- Tests and checks: targeted test files pass, and each new test was checked to fail without its fix.
tc:node,tc:web,tc:cliand the changed-code quality gate pass. CI: all checks pass on0946766240c(20 passed, 18 skipped). One test shard was rerun once after its runner was shut down mid-job, with no failing test.
Not verified
- Windows, Linux and WSL: no platform-specific code changed, and none was run.
- Mobile: the phone app reads none of the changed fields, so it wasn't run.
- SSH workspaces have no native chats.
- Opening a paired-host chat that is closed on that host.
- Mixed versions (an older desktop with a newer host): checked in code only.
- After a "Change Title", the Vault row's preview briefly read "No conversation preview available". This wasn't compared against main.
ELI5
Native Claude and Codex chats now get a name after their first message (#25724). The chat's tab and sidebar row showed that name cleanly, but AI Vault (the session history panel) needed a large pile of extra code to keep the name from disappearing, and Vault's search results never showed the name at all. This PR gives the app one place to learn a chat's name from the computer that runs it, makes the tab, the sidebar row, and Vault's list and search all read that place, and deletes the extra Vault code.
The problem
A chat's name is stored in exactly one place: the chat's saved record on the computer running it (
AgentSessionRecord.conversationName, written only bycompareAndSetConversationName). The tab strip and sidebar read it through the tab, which the host retitles from that record.AI Vault had no such path. To make a name appear and stay in Vault, #25724 copied the name into Vault's cached list results, into the list currently on screen, and into list requests still in flight, then added code to repair those copies when:
That was about 720 lines of renderer code (
ai-vault-structured-title-projection.ts,ai-vault-structured-title-recovery.ts,ai-vault-pending-title-projection.ts, plus additions to the result cache, applied-scan, refresh hook, tab-title sync gate, both tab-snapshot apply paths, a host title-lookup branch, and an extra field on tab snapshots). The root cause: a name that changes on its own was stored inside caches whose lifetime belongs to transcript scans and open tabs.Separately, Vault's indexed search ignored native chats completely: a search hit showed the transcript's first-prompt title, never the generated name or the user's manual tab rename.
What Changed
What you see
How
conversationNameread from the record. When a name is saved, the host republishes that chat's summary, including for a chat whose tab already closed.useStructuredConversationName(hostId, chatId), reads the name from it. The tab strip, the sidebar row, and the Vault row (list and search) all use it with one shared display order (structuredChatDisplayName).projectStructuredAiVaultSessions, unchanged in behaviour) stays as the fallback for chats the feed has not published this run.projectStructuredAiVaultSearchResponse), sharing one owner index with the list projection (which previously rebuilt the record list once per row). A paired client that does not advertise structured-session support gets its hits unchanged, as before.structuredConversationTitlefield on tab snapshots. Those files are back to their pre-Name native Claude and Codex chats after their first message #25724 form.Line count (production code on the naming path, excluding tests): +381 / −1059, net −678. Tests: net −1338 (the old repair tests go with the code they tested; new tests below).
Why this approach
Compared with the common pattern (a per-environment stream of thread metadata that the client folds into one map, which every thread list reads):
customLabel), not a saved rename of the conversationOlder paired hosts
Assumption, stated explicitly: #25724 has not shipped in any release yet (latest is v1.4.221), so no released host or client depends on the removed
structuredConversationTitletab-snapshot field or the native title lookup, and removing them is not a wire break.conversationName(any released host)How "older host" is detected: the new summary field is optional, so its absence is the signal; no separate capability or probe is needed, and an older host is never re-asked. Version data at the time of writing: about 80% of installs run a release from the last ~10 days and about 4–5% are a month or more behind. No new stream opcode or RPC method was added; only optional fields (see
docs/reference/remote-wire-compatibility.md).Linked Issue
Follow-up to #25724.
Visual Proof
Hidden Electron run on an isolated profile, with stand-in Claude/Codex CLIs and a stand-in naming command, head
eb99352a423. Every frame below was inspected. There is no "before" frame for search: before this PR a search hit showed the first-prompt text and never the chat name, which is the bug described above.Tab closed right after naming: no flash back. Named (tab, sidebar row and Vault row all say "QA Feed plum") → tab closed, Vault row keeps the name:
Tab closed before naming finished. Closed while still "Codex Chat" → the same Vault row becomes "QA Feed cedar" by itself (no tab, no Vault refresh):
Manual rename wins in the Vault list and search, and the generated name returns in search once it is cleared:
Indexed search shows the generated name of a closed chat (searched "Feed plum", hit titled "QA Feed plum"):
Closed chats keep their names in the Vault list, including ones named before this app launch ("QA Main willow", "QA Main quartz", "ALT Settings fern"):
Testing
tc:node,tc:cli,tc:web, fulloxlint, and the changed-code quality gate pass.Review
Agent skill upstream boundary
Notes
Security: no new inputs beyond optional, bounded fields from a paired host. Performance: one more optional string on an existing summary; the list ownership projection now builds one index per request instead of re-listing records per row.
Checklist