fix(desktop): know a Runtime Host target's identity before it connects - #5606
Merged
Merged
Conversation
Astro-Han
force-pushed
the
fix/desktop-host-identity-before-connect
branch
2 times, most recently
from
September 23, 2026 03:01
4591757 to
ec463f0
Compare
Astro-Han
marked this pull request as ready for review
September 23, 2026 03:17
Astro-Han
force-pushed
the
fix/desktop-host-identity-before-connect
branch
4 times, most recently
from
September 23, 2026 04:14
8cb9c69 to
587bcf0
Compare
A target generation published its scope to the renderer while connecting, but the manager only learned the target's hostId and created its reconnect lifecycle after the first connection. waitUntilReadyForScope therefore rejected every scope main itself had published, so each Host-scoped read during a cold start failed fast instead of waiting. The hostId is known when the generation is created (a remote profile's rootId, or the Local storage root), and the lifecycle can exist before it starts. Give every generation both from creation, and drop the places that re-derived the hostId. The renderer's boundary read retry schedule only papered over this window. Replace it with the event that actually invalidates an unreadable boundary: the session's Host reporting ready. The read model moves into the conversation feature so it reaches the bridge through its services port. Generated-by: Claude Code
Astro-Han
force-pushed
the
fix/desktop-host-identity-before-connect
branch
from
September 23, 2026 04:46
587bcf0 to
8611e4f
Compare
Contributor
|
LGTM |
orangeCatDeveloper
approved these changes
Sep 23, 2026
6 tasks
This was referenced Sep 23, 2026
2 tasks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
On a cold start with a task open, the desktop shows "暂时读不到这个任务的权限" (boundary unreadable) and toasts for "刷新模型连接失败" and "载入本地记忆状态失败". They share one cause: every Host-scoped read issued before the Local Runtime Host finishes its first connection fails immediately instead of waiting.
Root cause
A target generation publishes its scope (
epoch+hostId) while it is still connecting, but the desktop manager only learned the generation'shostIdand created its reconnect lifecycle after the first connection succeeded.waitUntilReadyForScopetherefore rejected scopes that main itself had published, and theawaitReadygate in preload turned every early scoped read into an error. The two routes that tell the renderer about scopes also disagreed: theruntime-host:identitiespull and theruntime-host-profiles:changedpush each re-derivedhostIdfrom different places.The renderer's bounded boundary-read retry (#1629) and similar waits hid this window. #5494 made the window visible because it paints before the Host is ready.
Fix
The
hostIdis known when a generation is created: it is a remote profile'srootIdor the Local storage root id. A reconnect lifecycle can also exist before its first connection.createRuntimeHostReconnectLifecyclebuilds a lifecycle that exists beforestart().waitForCurrentparks until the first candidate is installed and rejects if the start fails permanently.startRuntimeHostReconnectLifecycleis nowcreate+start.hostIdandlifecyclefrom creation. This removes#startLifecycle,#requireLifecycle, the post-connecthostIdassignment, and the optional-hostIdspreads in boot, the profile service and the IPC pushes. The pull and the push now read the same field.readExecutionBoundaryWithRetryand its schedule are removed.ready, which is the event that actually makes the failure stale. The listener is armed for the whole active session, not only after a read fails, because thereadypush can arrive before the read on the replaced generation fails.useActiveExecutionBoundarymoves intofeatures/conversation. It now reachessessions.readExecutionBoundaryand Host changes throughConversationServicesinstead ofwindow.maka. The renderer architecture ratchet does not allow the root-level hook orapp-shellto take on a new bridge subscription.runManagedLocalHostChangerefuses while the Local target is still on its first connection, because suspending the lifecycle cannot hold back a connection that is already in flight.Behavior change
hostIdin the push, which matches the pull. If the default Host fails to start, default-Host reads can surface their own error instead of being silently dropped. Targets that had connected at least once before already behaved this way.stopSessionand bot message delivery issued before the Local Host's first connection now wait for that connection instead of failing with "target has not started". Both call sites are fire-and-forget, and the wait rejects if the first connection fails permanently or the generation closes.hostId, preload no longer re-registers the scope of a removed target.mainthe timer retry recovered only when that happened within 1.75 s.awaitReadykeeps its 15 s timeout. The preload active-Host wait and theisDefaultRuntimeHostResolvableguards stay because they also cover Hosts that are unavailable, not only ones that are starting.Verification
runtime-host-desktop-manager.test.ts: 60/60 pass. Two new tests fail onmain: a scope published before the first connection waits for that connection, and it rejects with that connection's failure.active-execution-boundary-read-model: two new hook-level tests renderuseActiveExecutionBoundarybehindConversationServicesProviderand emit Host changes.ready, only areadypush for the session's own Host re-reads, and the boundary recovers. This fails when the re-read effect is disabled.readyarrives while the failing read is still in flight, the boundary still recovers. This fails when the listener is armed only after a failed read.runtime-hostreconnecting-connectionran 25/25.typecheck:stories,npm run format,npm run lint, andcheck-renderer-architecture --strict-baseagainstorigin/mainpass.session-local-recoveryandnew-task-reloadpass (5/5).main, both toasts ("刷新模型连接失败", "载入本地记忆状态失败") appear.AI use
Tool(s) and scope: Claude Code (Opus 5.5) diagnosed the issue over CDP, wrote the fix and tests, and ran the verification above.
Checklist
Does this PR entail a change in behavior?