Skip to content

Show one actionable error when an agent launch cannot prepare its workspace - #37220

Merged
brennanb2025 merged 40 commits into
mainfrom
brennanb2025/launch-double-toast-1101
Nov 8, 2026
Merged

brennanb2025 merged 40 commits into
mainfrom
brennanb2025/launch-double-toast-1101

Conversation

@brennanb2025

@brennanb2025 brennanb2025 commented Nov 1, 2026 •

Copy link
Copy Markdown
Contributor

The problem

Starting an agent from New Chat, a shortcut, Hand Off, Source Control or Fix Checks could show two error toasts for one failed start. The step that prepares the launch showed its own error and returned nothing; every caller then treated "nothing" as an unknown failure and showed a second, internal-sounding "Could not build launch command" message. "Nothing" also meant the user had cancelled, so callers were guessing. While a remote host was still connecting, clicking a terminal choice also produced an error telling the user to connect, although it was already connecting.

What changes for users

  • A failed start shows exactly one message, naming the requested or selected agent and saying what to do (connect to the SSH host, reconnect or update the workspace's Orca server, refresh projects, open a terminal and start the agent there, or fix an argument with an unclosed quote). Before an automatic agent choice is known, Start work keeps its existing operation recovery without guessing an agent name. Cancelling shows nothing.
  • While the workspace's host is still connecting or setting up, terminal choices stay visible but disabled with "Connecting…", in the New Chat menu, its setup panel, the agent picker and the agent combobox. Chat and Configure stay usable, and an agent whose preferred surface is terminal stays on terminal. Keyboard shortcuts and late clicks during that window do nothing instead of showing an error.
  • A chat that a host supports is no longer refused because terminal-only launch information is missing.
  • When a visible chat is refused by its host and the terminal fallback can't start either, the chat's own failure notice carries the advice instead of a second toast.
  • The Hand Off dialog stays open (with its request) if nothing actually started, and can always be closed while a start is in progress.
  • Fix Checks also reports failures before workspace creation once, including when the pull request cannot be read. Opening an existing fallback dialog or workspace view still works.
  • Connected SSH hosts missing launch information are tracked in STA-13357.

How it works

Launch preparation now only returns facts (the host's OS, or why it isn't known and whether that is still pending). The launch action is the single place that reports a failure: it shows one localized, named-agent message and returns the existing refused result with a notification receipt, which every caller now checks explicitly instead of inferring failure from an empty result. Chat routing runs before the terminal-only OS check. Fix Checks uses its existing per-attempt notification callback so the action and its fallback cannot repeat the same failure. The menus derive one "terminal still waiting" value from the host's live connection state and apply it where a terminal choice is shown or chosen; it is not stored and is kept out of the picker's identity keys, so it never resets drafts or inventory.

Tracked in STA-13205.

Review path: full. This changes launch failure handling and routing order and has a reviewed plan; it is outside the light path's local-effect and low-risk criteria. The change introduces no saved field or wire format.

Intended: terminal launches still require the execution host's reported launch information to preserve shell quoting; chat admission does not depend on it. Intended: only a host that is still connecting or setting up disables terminal choices; settled missing information (STA-13357) still reaches the one accurate refusal, per the coordinator's scope ruling. Intended: the readiness value is per menu rather than per agent row, and chat and terminal readiness are separate, because Orca offers both surfaces for one agent.

Files Added Deleted Net
Test 16 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1604 $\color{#cf222e}{\Huge{\mathbf{−}}}$​146 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1458
Prod 64 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​1137 $\color{#cf222e}{\Huge{\mathbf{−}}}$​619 $\color{#1a7f37}{\Huge{\mathbf{+}}}$​518

…onnects

Replace per-row pending metadata with one derived terminal-readiness flag on
the agent menu model, kept out of picker identity keys. Terminal buttons and
rows stay mounted and disabled while SSH or a paired server is still
connecting; chat and Configure stay usable, and the terminal default is not
changed. Repo targets with no workspace yet read the repo host's facts.
Invalid Source Control arguments keep their existing actionable message.
…ckers and fallbacks

A host that is still connecting now declines a terminal launch silently at the
launcher, including shortcuts and fallbacks, instead of reporting a failure.
A terminal fallback refused behind a visible chat reports through that chat's
notice rather than a second toast. The agent picker stays usable while only a
terminal waits; an armed terminal rechecks readiness on Enter; a configured
chat option no longer inherits the terminal's waiting state; waiting terminal
controls say why. Invalid CLI arguments name the actual problem for every
caller, the no-agent shortcut message is restored, and prospective repo
readiness no longer rescans repos on unrelated store writes.
…thing

A session continuation launched through the host's admission now waits for
that launch to settle before reporting the handoff. When the host declines the
chat and its terminal fallback is declined while the host is still connecting,
nothing opens, so the dialog stays open for a retry instead of closing with no
session and no message.
Cancel, Escape and outside clicks now close the Hand Off dialog even while a
start is in flight; Start stays disabled with its spinner. Closing aborts agent
detection if it is still running, and a dialog closed during detection starts
nothing. A launch already sent settles on its own and reports through its
existing owners. The in-flight start belongs to the request it was made for, so
a late finish never closes, completes or blocks a dialog reopened since.
@brennanb2025
brennanb2025 marked this pull request as ready for review November 2, 2026 04:10
@coderabbitai

coderabbitai Bot commented Nov 2, 2026 •

Copy link
Copy Markdown
Contributor

Review in Change Stack →

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • Resume reviews
  • Trigger review

No actionable comments were generated in the recent review.

Walkthrough

The changes derive terminal-pending state from execution-host facts and use it to gate terminal choices in agent menus while keeping available chat choices usable. Launch preparation failures now return explicit refusal outcomes with recovery guidance, and callers handle refused results. Structured terminal fallback results include refusal details. Continuation dialogs can close during a pending launch, and late or aborted completions do not change the current dialog.

Merge Risk: Low · previous review f60ce

Tests may become order-dependent because the Windows platform stub leaks into later tests. The change is otherwise mergeable with this test-isolation follow-up.

Pre-merge checks: Passed 3 · Failed 2

Failed Failed checks: 2 warnings

Check name Status Explanation Resolution
Docstring Coverage Warning Docstring coverage is 43.08% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documenta… Write docstrings for the functions missing them to satisfy the coverage threshold.
Description check Warning The description explains the problem, user impact, and mechanism. It omits required visual proof, testing details, and a risk assessment. It also lacks a linked issue and several template sections. Use the template headings and add a plain-language ELI5. Explain why this approach is better than the alternatives. Link the issue if required for the contributor. Add before-and-after visual proof, reviewer test steps, tested platforms, an…
Passed Passed checks: 3 passed

Check name Status Explanation
Title check Passed The title names the main change: when an agent launch cannot prepare its workspace, the user sees one actionable error instead of a second generic toast. The changeset matches this. Launch preparation…
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.
Full details: Title check

Explanation

The title names the main change: when an agent launch cannot prepare its workspace, the user sees one actionable error instead of a second generic toast. The changeset matches this. Launch preparation now returns facts, the launch action reports one localized message naming the agent, and callers check the explicit refused result. The title does not cover the terminal-pending readiness work, which the description covers as a secondary part.

Full details: Docstring Coverage

Explanation

Docstring coverage is 43.08% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 65 functions across 66 files.

Resolution

Write docstrings for the functions missing them to satisfy the coverage threshold.

Full details: Description check

Resolution

Use the template headings and add a plain-language ELI5. Explain why this approach is better than the alternatives. Link the issue if required for the contributor. Add before-and-after visual proof, reviewer test steps, tested platforms, and test command results. State the risk level, affected users and boundaries, and rollback plan. Complete the applicable disclosure, review, skill-boundary, and checklist sections.

Finishing Touches

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

Recent review info

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 43eaf1a9-9534-4e0c-8aba-eab920270973
Commits

Reviewing files that changed from the base of the PR and between a76283e and f60ce6c.

Files selected for processing: 1

  • src/renderer/src/lib/launch-agent-structured-chat-test-fixture.ts

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

The terminal readiness check rebuilt the target's ownership chain and ran
the full OS resolver on every store write, for every mounted picker. Each
picker now keeps its target host and re-resolves it only when an ownership
slice changes; other writes read just that host's connection and contact
facts. Local hosts read nothing. The launcher's own pending decline uses
the same host pieces from execution-host-facts, so the rule has one copy.
Wording the failed chat's notice after its terminal fallback was refused
re-saved every retained launch record, though the wording itself is never
saved. Listeners now hear about it without a save; lifecycle changes still
save. The refused-fallback test moves to its own file and counts saves at
two sizes.
launchAgentInNewTab now returns a refusal instead of a tab when the
launcher declines, so tests that read the opened tab's surface or
completion narrow it first. Fixes the web typecheck.
While the host connected, each New Chat agent row was marked
aria-disabled, so its Configure button read as disabled to assistive
tech and keyboard. The row now only blocks its launch and points to its
"Connecting…" reason. A row shows one reason, the one that stops its
launch now, and the agent name keeps its width so it is not cut to a
single letter.
…rf contract

Pickers for workspaces not created yet now count repo reads cold and after
another host's row is republished, at two scales with a repeated-target
control, and assert no unaffected renders. Two freshness controls follow
the cached host: an owner moving from local to a connecting SSH host and
back, and a paired server that is still checking versus verified with its
OS.
… stay usable

An agent row's launch item also held its Configure button (New Chat) or
its start-in-chat button (the + menu). Disabling the row while the host
connected disabled those too; leaving it enabled made the waiting launch
read as available and then do nothing.

The launch item and those controls are now siblings. While the host
connects the launch item is marked unavailable, dimmed and described
"Connecting…", and stays focusable so arrow keys still reach the
controls beside it. Configure and chat keep their own state. A chat
started from Configure or the chat button goes through the row's guarded
launch and closes the menu, without choosing the waiting item.
…ancelled

Each press on the button beside an agent row added a window listener that
only a pointerup removed, so cancelled touches and closed menus left them
behind. A press now owns one handler that removes both listeners on
release, cancel, the next press or the row closing; drag release onto the
button still chooses it.
The launch item beside Configure or the chat button restyled the shared
menu item (transparent focus, dimmed while unavailable), which the design
system gate rejects. That treatment is now the menu item's launch-row
variant; the rendered rows are unchanged.
…a checks

The menu's terminal readiness now reads workspace records through the
workspace record selectors and picks a not-yet-created workspace's repo by
the host it names, without the focused-host fallback; with no host named
it settles only when the id has one row, which reads the same. Its cached
host is re-checked when the settings slice changes instead of reading the
focus setting. E2E unit tests narrow refused launch outcomes, and the two
new ja strings write Agent as the rest of the catalog does.
A refused launch now returns its refusal instead of null, so the parity
check asks whether a surface opened.
… fallback

Terminal readiness re-resolved every mounted menu's host on any settings
write, and an unstamped worktree whose project is listed both here and by
a paired server walked the whole repo catalog to resolve. Readiness now
resolves an existing workspace's host from its resource owner only, never
the focused default a row-less setup terminal is created on, so settings
writes leave it alone. The shared-project check reads that project's rows
from the existing per-snapshot repo index instead of the whole catalog.
Setup terminals and every route keep their behavior.
The detected-twin perf contract now checks render deltas per update: a
catalog republication renders no picker, and stamping the first workspace
with a checking server, then that server reporting its OS, renders only
that workspace's pickers.
…ce host

The fixture replaces the whole worktree owner module with a stub, so the
menu readiness check's new resource-host export was missing and the
picker path threw before launching. The stub now answers both exports
with the scenario's owner host.

@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.

Follow-up Comments

Outside diff Mock remote detection in the performance fixture.

571-580: Major · Stability & Availability · Quick win

Mock remote detection in the performance fixture.

The SSH mount reaches the missing window.api.preflight.detectRemoteAgents bridge. The unhandled error fails the ordinary unit command. Add the typed mock below.

Suggested fix
       settings: { ...getDefaultSettings('/home/me'), agentDefaultSurface: 'terminal' },
       remoteDetectedAgentIds: { box: ['codex'] },
-      sshConnectionStates: direct(connecting('box'), connecting('other'))
+      sshConnectionStates: direct(connecting('box'), connecting('other')),
+      refreshRemoteDetectedAgents: vi.fn<AppState['refreshRemoteDetectedAgents']>(
+        async () => ['codex']
+      )

Open the source location

Nitpick Measure renders for the full picker observation.

797-800: Trivial · Performance & Scalability · Quick win

Measure renders for the full picker observation.

The detected-twin test currently counts only createAgentMenuTerminalPendingSelector renders. useAgentMenuObservation has additional subscriptions, so the readiness assertions do not establish the complete picker render contract for catalog republication or readiness updates. This is a missing coverage case, not an observed current regression. Reuse mountPickers or an equivalent full-observation counter for this scenario.

Open the source location


Review info
Run configuration
  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Repository UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: 2dd2369f-03cf-4673-95e0-3b857a48a512
Commits

Reviewing files that changed from the base of the PR and between ff0cc19 and a76283e.

Files selected for processing: 1
  • src/renderer/src/components/tab-bar/use-new-tab-agent-menu-rows-owner.test.tsx

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

@brennanb2025

brennanb2025 commented Nov 6, 2026 •

Copy link
Copy Markdown
Contributor Author

Starting an agent could report one failure twice: launch preparation showed an error, then its caller treated the empty result as another failure. Cancellation used that same empty result. The launch action now reports one localized message with recovery advice, names the requested or selected agent, and lets callers recognize an already-reported refusal. Cancellation and a host still connecting stay silent.

While a host is preparing, terminal choices remain visible and disabled with a reason. Chat and Configure remain usable, drafts survive readiness changes, and the selected surface stays unchanged. Start work and Fix Checks also report failures before workspace creation once, while preserving their functional fallback actions. Existing native submenus, menu closing, host routing and shell selection remain intact. No saved field or wire format changed.

Independent Claude Opus-medium and Codex-high reviews are complete on 5b5b5b8c98d767b57bf25f4d4f0982ca31dadb5e, with no open required findings. The final reference comparison and independent performance review are complete. Performance proof retains N/10N operation bounds, render isolation and live invalidation checks. Intended differences for multiple hosts and separate chat/terminal readiness are documented in the PR.

After main moved again, merge 02c15d63e2023570bb62590239dedae57e928d0a brought in main 228263d5664fca3ece26171dbeaa87ff24517d27, following the previously approved sync cc25a3108b540299bb7c44352d1f164084a1e405. The conflict resolution preserves main’s retained app-state views and observer lifecycle, Source Control placement, startup directory and workspace-trust behavior. It also preserves the approved terminal-pending selectors, stable menu drafts, explicit launch refusals and single action-owned recovery message. Main’s removal of the background launch argument stays in place. The unchanged unavailable-host refusal branch moved into the existing refusal module to satisfy the combined file-size check, and existing test narrowing wraps the new accepted host-launch cases without changing their assertions. The staged-file lint, plugins, source scans and formatting checks passed before the merge commit. No dependency install was needed.

A further main update was merged in c2b0608350536069f314329df3d441845b06181a, bringing in 08c8c33026b2116e2b151a29fe57baa3095155a3. Its only conflict was the import block at lines 1–4 of the direct work-item launcher. The resolution keeps main’s setup-approval helper and the existing failure-message and setup imports; no launch logic changed. Both continuing independent reviewers checked that hunk in one pass, confirmed that main’s automatically merged setup-approval call is preserved, and found no issues. Main was then merged without conflicts or manual source edits in 20b941a802b43c05bf09dd9e8ecad1b4cf7d4e19, bringing in 5adbcac51122a47fe465288f6169171128a4725f and #39426’s registry lint correction. Both continuing reviewers independently verified the empty resolution diff and unchanged launch patch, with no findings. The earlier five conflict resolutions and launch/view boundaries remain reviewed. The existing screenshots, renderer/integration tests, references and performance proof carry. No additional QA run was needed. Merge-specific staged-file checks passed again, with no dependency install.

Main moved again and was merged in 401335abbb17c46b7c6acf6b8eb7033f61e85701, bringing in cecfa9865d1e03603fdb27efde5966ee73be4c8b. All four conflict blocks were imports: the menu-owner test, menu observation hook, structured launch admission, and paired-web launcher test. The resolution follows main’s moved runtime owners while retaining the launch refusal and facts imports. Five supporting import or mock paths in lane-only code were adapted to those same owners; the obsolete direct revision import stays removed because its call lives in the extracted facts selector. Both continuing independent reviewers checked the exact commit in one pass and found no changes to launch logic, test bodies or assertions. All moved modules remain owned by main; none was restored. The four affected named suites from that sync passed 48 tests, and merge-specific staged-file checks passed. No dependency install or additional app run was needed; the existing QA, reference and performance evidence carries.

A further conflict-only merge 5f4e1121d1b6bae861668845d073cea74f6ee6d6 brings in main bc215d95ec0dc38f367fc156f7c4259b96025f81. The two conflicts retain the moved composer-focus type and main’s extracted direct-launch failure reporter. The extracted reporter reads the same current requested or resolved agent when a failure is reported, preserves the approved named-agent message, and calls the existing notification callback once. Main’s new direct-start inputs and accepted prompt-delivery path remain intact. Seven supporting imports and mocks now target main’s moved owners. The combined file’s first CI run found that formatting expanded one argument-destructuring declaration past its existing file-size limit. Commit 15d974882aacd604a99758c98805c9913159adb9 restores the two original declarations with identical field-read order; the normal hook passed and the continuing Codex reviewer verified that narrow correction. Claude’s unchanged receipt carries across it. The six affected named suites passed all 48 tests. Main’s reporter extraction and routing exports remain intact. Both continuing independent reviewers checked the exact merge in one pass and found no issues; no launch outcome, assertion or user flow changed in the resolution. Unchanged UI evidence carries. No dependency install or new app run was needed.

Validation: current-head CI is green, including static analysis/typecheck, all 12 unit shards, both packages, layout checks and changed end-to-end specs. Managed-SSH checks were not selected by this run; prior unchanged evidence carries. Five focused merge suites passed 45 tests; the final launcher and paired-web suites also passed on the committed contents. Retained focused tests cover every changed launch surface, refusal/cancellation/pending outcomes, selected-host agent identity, prompt retention and a literal French recovery message. The web-runtime suite passed 10/10. The previous sync’s first CI attempt had a worker SIGSEGV while running the unchanged plugin reconciliation test, without a failed assertion. One failed-job retry passed that shard and the aggregate; no source or test assertions changed for the retry, and the crash cause remains undiagnosed. The merge-specific staged-file checks passed.

QA: the retained native menu check at 5b5b5b8c98d767b57bf25f4d4f0982ca31dadb5e passed English/dark and French/light, pending and settled, on Windows. All four screenshots show full left-aligned labels; pending rows have one linked reason, disabled primary launch and enabled Configure, while settled rows have no reason. Enter and Space while pending opened no agent tabs and produced no toast. The Codex driver matched the recorded app identity. The paired server and client are stopped, their ports are clear, and real config hashes are unchanged. The prior branch-only screenshot run passed Configure pending→settled with the draft preserved, French/light Configure, and searched Configure reopened by Enter. Those unchanged flows were captured at 94b0ac14be4c357f65a9a37051cfee851d0022a7; subsequent tests-only commits preserve their evidence. The label alignment change has its own row proof at that head; the later main merges preserve it. These are the UI/tests rows of the QA table; no agent was launched for screenshots. Launch outcomes are covered by renderer/integration tests.

Deferred: a trivial launcher pass-through alias (P3), and test-only cleanup to restore the original window API after a Windows fixture. The continuing independent reviewer traced every subsequent case: its remote, performance and liveness proof remains intact. A separate automated claim that the settlement helper returns null was refuted: the conditional expression returns undefined for cancellation. No shipped correction or weakened assertion resulted from either comment. The existing settled SSH missing-platform issue is tracked separately in STA-13357. The inherited registry lint blocker is corrected by main; the current-head CI run is green. The coordinator owns a separate performance check requested before merge; this status does not claim that separate check is complete.

For Brennan’s review:

Native menu while its host is connecting (English/dark):

Native menu pending

English/dark after readiness · French/light while connecting · French/light after readiness

Configure while terminal is waiting (Chat stays available):

Configure pending

Configure after readiness, with the draft preserved · French/light Configure

Searched Configure retains its draft and reopens with Enter:

Search Configure

@brennanb2025
brennanb2025 marked this pull request as draft November 7, 2026 08:20
@brennanb2025
brennanb2025 marked this pull request as ready for review November 7, 2026 08:20
@brennanb2025
brennanb2025 marked this pull request as draft November 8, 2026 02:49
@brennanb2025
brennanb2025 marked this pull request as ready for review November 8, 2026 02:49
@brennanb2025
brennanb2025 marked this pull request as draft November 8, 2026 03:00
@brennanb2025
brennanb2025 marked this pull request as ready for review November 8, 2026 03:00
@brennanb2025
brennanb2025 merged commit 00e25b5 into main Nov 8, 2026
89 of 109 checks passed
@brennanb2025
brennanb2025 deleted the brennanb2025/launch-double-toast-1101 branch November 8, 2026 03:21
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