Skip to content

fix(native-chat): every new-chat request opens its own chat - #26782

Merged
brennanb2025 merged 4 commits into
mainfrom
brennanb2025/fix-second-native-chat
Oct 9, 2026
Merged

brennanb2025 merged 4 commits into
mainfrom
brennanb2025/fix-second-native-chat

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor
Files Added Deleted Net
Test 5 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​348 $\color{#cf222e}{\Huge{\mathbf{−}}}$​679 $\color{#cf222e}{\Huge{\mathbf{−}}}$​331
Prod 13 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​20 $\color{#cf222e}{\Huge{\mathbf{−}}}$​256 $\color{#cf222e}{\Huge{\mathbf{−}}}$​236

ELI5

If you opened a native chat (Claude, Codex, OpenCode or another native-chat agent) and then asked for another chat with the same agent before sending anything in the first, Orca did not open a second chat. It brought the first, empty chat back to the front, or folded your pick into it if it was still starting. The same happened when you sent notes or a browser annotation to "New agent" while a blank chat of that agent was still starting: the notes went into that blank chat instead of a new one.

After this change, every "new chat" request opens its own chat.

What Changed

Before / after, as the user sees it

Situation Before After
Pick Claude from the + menu, send nothing, pick Claude again (same for the new-tab search, the new-agent shortcut and the dashboard) The first, empty chat is focused again. No second chat. A second Claude chat opens and is focused.
Same, but the first chat is still starting The second pick joins the starting chat. No second chat. A second chat opens.
"Send notes to > New agent" (or a browser annotation sent to a new agent) while a blank chat of that agent is still starting in the same split The notes become the first message of that blank chat; no new chat opens. Once the blank chat had finished starting, the same action opened a new chat, so the result depended on startup speed. The notes always open their own new chat. The blank chat stays blank.

Mechanism

#24917 let a new-chat request end up in an existing chat in three ways. This PR removes two of them:

  • An idle, empty chat was refocused. beginStructuredAgentLaunchSettlement asked findIdleEmptyStructuredChat for an open chat nothing had been sent into and returned it instead of starting a launch. That lookup and its file are deleted.
  • A chat still starting was joined or claimed. getJoinableStructuredLaunchState let a blank request join, and a request with text claim, the newest blank chat still starting in the same tab group. It now only returns a launch that the same request already started (structuredLaunchesHoldingIdentity(matches, request.id)).
  • Kept: the same request delivered twice joins the chat it already opened. Every pick carries a request id minted where the action is handled, and a caller re-entering its own launch reuses it. This was the first check before and is unchanged.

Everything that existed only for the two removed paths is deleted: the claim branch in joinStructuredLaunchState, the blank flag on launch attempts, the launch request type with its text and tab-group fields, the tab-group field on launch options and targets (the tab itself still opens in the caller's split through the existing provisional-tab argument), the empty-chat check (structured-agent-session-launch-empty-chat.ts), the tab-group lookup (structured-agent-session-chat-tab-group.ts), and a draft-loading flag only the empty-chat check read.

Why

Orca shows chats as tabs. Asking for a new tab, or sending notes to a "New agent", is an explicit request for a new chat; quietly reusing an empty one looked like the pick had failed and made notes land in a tab the user had just opened for something else.

Removing the join paths instead of narrowing them is the smaller change: after this PR, the only way a new start joins another is a repeat of the same request, which is also how the host-side launch path de-duplicates.

Differences from the common pattern:

  • Intended: a new-chat request never reuses an empty chat. The common pattern keeps one unsent draft thread and routes "new thread" there; that draft costs nothing because no session exists until the first send. Orca creates the agent session and its tab on the pick, so each pick is its own chat.

Not in this PR:

  • A fast double click on a + menu item can now open two native chats, because each click mints its own request id and a closing menu still accepts clicks during its fade-out. Terminal agents already behave this way on main, so this matches them; where duplicate-click protection lives is part of STA-9920.
  • Native-chat new-chat launches still go through the window's own launch list rather than the host's agent.launch. Moving them is STA-9920.

Linked Issue

None. This is a maintainer request. Follow-up: STA-9920.

Visual Proof

Live QA on a separate Mac (hidden dev builds, isolated profiles), with the real OpenCode 2.0.21 talking to a local test model and its start delayed 6 s so "still starting" can be hit reliably. An in-page log sampled every chat's launch state every 20 ms to confirm the timing. After = this PR at 3c0e7ac; before = main (the before shots for the first two rows were taken on this Mac earlier at f01bd93).

Picking OpenCode twice from the + menu, 0.5 s apart, while the first chat is still starting

Before: still one OpenCode tab After: two OpenCode tabs
before-quick-double-pick after-two-picks

Picking OpenCode again once the empty chats are ready and idle

Before: the existing empty chat is focused, still one tab After: a third chat opens and gets focus
before-pick-after-idle after-pick-after-idle

"Send notes to > New agent > OpenCode" while a blank OpenCode chat is still starting

Before: one tab; the note went into the blank chat After: the blank chat stays empty After: the note opened its own chat
before-notes after-notes-blank-tab after-notes-own-tab

Testing

  • structured-agent-session-new-chat-own-launch.test.ts (renamed from ...-empty-chat-reuse.test.ts) and structured-agent-session-launch-different-request.test.ts: a second blank pick opens a second chat whether the first is idle or still starting; notes sent while a blank chat is starting open their own chat; notes delivered twice are sent once, in their own chat; the same pick delivered twice still opens one chat. Putting the text claim back fails 8 of these 17 tests; restoring the old idle-chat reuse fails the two "opens a second chat" tests.

  • 37 launch-related renderer test files, run one at a time: 290 tests pass.

  • orca-ci-checks: 23 of 23 pass, including the full typecheck. CI is green.

  • Live: the three scenarios above on macOS with OpenCode. Not run live: Linux, Windows, SSH, and other agents; the changed code is shared by every native-chat agent and every host.

  • 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

Renderer-only launch logic. Nothing sent to the host and no saved data changes, so SSH, WSL and mobile hosts are unaffected: the change only decides whether the desktop starts a new launch. Same behavior on macOS, Linux and Windows.

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)

…ivers the same request

A request with text ("Send notes > New agent", browser annotations) used to take over a blank
chat that was still starting in the same tab group, so the same action landed in the just-opened
blank tab or in a new tab depending on how fast the first chat started. Every start now opens its
own chat unless it re-delivers the same request id (or resumes the same conversation).

Removes what only that claim read: the claim branch in the launch join, the attempt's blank flag,
the request's text/group fields, the launch options' targetGroupId and the launch target's
groupId (the tab still opens in the caller's group), the empty-chat and chat-tab-group modules,
and the draft-load-pending probe only the empty-chat check used.
@brennanb2025 brennanb2025 changed the title fix(native-chat): a second new chat opens even when the first is still empty fix(native-chat): every new-chat request opens its own chat Oct 9, 2026
@brennanb2025

Copy link
Copy Markdown
Contributor Author

Review summary

Problem. Picking a native-chat agent (+ menu, new-tab search, new-agent shortcut, dashboard) a second time without sending anything did not open a second chat: Orca refocused the empty chat, or joined it if it was still starting. Notes or a browser annotation sent to "New agent" while a blank chat of that agent was still starting went into that blank chat, so the result depended on how fast the chat started.

User-facing change. Every new-chat request now opens its own chat. The same request delivered twice still opens one chat.

What changed in review

  • The first version stopped blank picks from reusing empty chats but kept the rule that lets a request with text take over a blank chat that is still starting. Review found that rule caused the same surprise, so it is removed too (3c0e7ac). That removal also deletes the code that only existed for it: the empty-chat check, the tab-group lookup, the blank flag, the launch request type and a draft-loading flag (+62/−358).
  • The PR description is rewritten for the final design. It no longer says a double click opens one chat (see below).

Deferred

  • A fast double click on a + menu item can open two native chats. Each click mints its own request id, and a closing menu still takes clicks during its fade-out. Terminal agents already do this on main, so it was left as is; STA-9920 covers where duplicate-click protection should live.
  • Native-chat launches still use the window's own launch list instead of the host's agent.launch: STA-9920.
  • Seen in QA, not caused by this PR: the "Send notes to" menu labels a chat that is still starting as "Idle". On main, the notes menu item stopped responding once when a new chat tab appeared just after the menu opened (not retested on this branch).
  • Small cleanups: a launch-prompt helper that only tests read now, and a request file that now holds only the attempt type.

Verified

  • Three review rounds, and a readiness pass over the final diff: no blocking issues. Covered SSH/remote, crashes and leaks, security, performance, functional behaviour, saved data and mobile, cross-platform, and code layout.
  • Nothing sent to the host or saved changes; no mobile code is touched.
  • Tests: 290 tests in 37 launch-related renderer files pass. Putting the text claim back fails 8 of the 17 tests in the two changed test files.
  • orca-ci-checks: 23 of 23 pass, including the full typecheck. CI is green.
  • Live on a separate Mac, with real OpenCode and a 6 s start delay, and the launch state logged every 20 ms:
    • two picks 0.5 s apart open two tabs;
    • a pick beside idle empty chats opens a third tab and focuses it;
    • notes sent while a blank chat is starting open their own tab and leave the blank chat empty, on two runs;
    • on main, the same notes went into the blank chat.

Not verified: Linux, Windows and SSH hosts, and agents other than OpenCode, were not run live. The changed code is shared by every native-chat agent and host.

@brennanb2025
brennanb2025 marked this pull request as ready for review October 9, 2026 22:26
@coderabbitai

coderabbitai Bot commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration
  • Configuration used: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: b795cdda-93d5-4b14-9beb-181b68229fd8

📥 Commits

Reviewing files that changed from the base of the PR and between 3c0e7ac and 425d85c.


📒 Files selected for processing (1)
  • src/renderer/src/lib/structured-agent-session-launch.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.



📝 Walkthrough

Walkthrough

Structured agent launches now start separate chats instead of claiming an existing idle empty chat. Repeated attempts with the same request ID can join the existing launch. The launch request and target interfaces no longer carry the removed blank-state and target-group fields. Tests cover separate launches, repeated requests, prompt delivery, and split targeting. Draft-load tracking now uses the hydration promise, and the pending-state helper was removed.


Priority: ⬇️ Low

Merge Risk: ⚪ Minimal · up to 425d8

Distinct new-chat requests open separate chats, duplicate delivery reuses its request’s chat, and draft loading retains its retry and bounded-wait behavior. No material merge risk is established.

Pre-merge checks | Passed 4 | Failed 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 46.67% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 15 functions across 8 files. 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 clearly and concisely summarizes the main change: each new-chat request opens its own native chat.
Description check Passed The description is complete and focused. It explains the user-visible behavior, implementation changes, rationale, testing, visual proof, deferred work, compatibility considerations, and checklist sta…
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.


  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 2
📝 Generate docstrings 💡
  • Commit to this branch
  • Create a new PR


🛠️ Fix failing CI checks 💡
  • 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.

…nd-native-chat

# Conflicts:
#	src/renderer/src/lib/structured-agent-session-launch.ts
@brennanb2025
brennanb2025 merged commit a9458b5 into main Oct 9, 2026
51 of 53 checks passed
@brennanb2025
brennanb2025 deleted the brennanb2025/fix-second-native-chat branch October 9, 2026 23:05
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