Fix/per profile remote ws routing - #85750
Tigrannnnnnn wants to merge 6 commits into
Conversation
|
This was generated by AI during triage. Summary: Problems:
Solution: Evidenceno deterministic fact backs this claim — model belief, not executed or read evidence Checked against |
|
Independent confirmation that this discriminator fix also resolves the local pooled profile case, not just per-profile remote overrides. With local profiles ( Verified locally with a shared-primary discriminator (tested as |
|
Test updated per triage; overlaps with #85778 — happy to close in its favor if maintainers prefer one patch. |
|
Thanks for this fix. I independently reproduced the per-profile remote override case addressed here. Configuration shape:
Observed behavior:
I validated the proposed discriminator against the focused Desktop tests:
Command: cd apps/desktop
npm test -- --run \
src/store/gateway-shared-remote.test.ts \
src/store/profile.test.tsThe explicit #85778 already adds the central I prototyped that test on this PR's current head with an isolated test fixture using If #85750 remains the canonical implementation, it may also be useful to add |
|
@cmoiccool Thanks for the independent validation — great to have the focused suite green at 13/13. Yes, please open the test-strengthening PR against my branch — happy to merge it in (test-only is exactly right). I'll add |
|
Tracking this for #85745 — same root cause as the profile-tab session list regression on 0.20.1. Core fix looks correct; happy to follow up with a separate wiring refresh PR after merge if sidebar still needs an explicit re-fetch on profile switch. |
test(desktop): pin profile-owned remote WebSocket target
|
Independent reproduction + end-to-end verification of this fix on the pure local-profiles case (no remote overrides, no global remote — the PR description frames the repro around per-profile remote overrides, but the same misclassification hits plain local pools too). Repro (v0.20.1, macOS arm64, local mode, 8 local profiles):
Downstream data damage worth noting for severity: because RPCs landed on the wrong backend while the UI showed another profile, sessions opened "in" a profile were actually created in the primary's state.db. We found 8 sessions born in the wrong profile's database this way (4 client sessions in default that belonged to hm, 2 in stock that belonged to hm, 2 in video that belonged to stock) and had to migrate them between profile state.dbs manually. Affected users may report this as "my project folders are in the wrong profile / missing" — it's this bug plus resulting data misplacement, not a projects.db issue. Verification of the fix: applied the same The marker approach covers the whole class since both |
|
Superseded by #86325, which consolidates the |
…0.20.1 sharedPrimaryRoute() inferred "served by the shared primary backend" from the mere presence of connection.profile. But pooled backends (a local named profile, or a per-profile remote override) also carry `profile` so their WebSocket URL mints against the right backend. Both descriptors looked the same, so ensureGatewayForProfile() took the shared-primary branch for a pooled profile and never dialed its socket — Desktop stayed on the default profile's socket even though the sidebar (REST) listed the right sessions. Regression from #85665 (d16e236). Tag only the true shared-primary descriptor with an explicit `sharedPrimary: true` marker in ensureBackend() and check that marker instead of `profile`. Covers both the local-pool and remote-override routes — the whole bug class, not one path. Test asserts both sides of the invariant: a { profile, sharedPrimary: true } descriptor activates the primary socket without dialing, and a pooled descriptor carrying { profile } dials its own exact WebSocket URL. Supersedes #85750, #85778, #85932 Fixes #85777 Co-authored-by: Tigrannnnnnn <122704900+Tigrannnnnnn@users.noreply.github.com> Co-authored-by: Don Tuttle <11698271+wdon@users.noreply.github.com> Co-authored-by: plcunha <145560011+plcunha@users.noreply.github.com>
…0.20.1 sharedPrimaryRoute() inferred "served by the shared primary backend" from the mere presence of connection.profile. But pooled backends (a local named profile, or a per-profile remote override) also carry `profile` so their WebSocket URL mints against the right backend. Both descriptors looked the same, so ensureGatewayForProfile() took the shared-primary branch for a pooled profile and never dialed its socket — Desktop stayed on the default profile's socket even though the sidebar (REST) listed the right sessions. Regression from NousResearch#85665 (977f744). Tag only the true shared-primary descriptor with an explicit `sharedPrimary: true` marker in ensureBackend() and check that marker instead of `profile`. Covers both the local-pool and remote-override routes — the whole bug class, not one path. Test asserts both sides of the invariant: a { profile, sharedPrimary: true } descriptor activates the primary socket without dialing, and a pooled descriptor carrying { profile } dials its own exact WebSocket URL. Supersedes NousResearch#85750, NousResearch#85778, NousResearch#85932 Fixes NousResearch#85777 Co-authored-by: Tigrannnnnnn <122704900+Tigrannnnnnn@users.noreply.github.com> Co-authored-by: Don Tuttle <11698271+wdon@users.noreply.github.com> Co-authored-by: plcunha <145560011+plcunha@users.noreply.github.com>
…0.20.1 sharedPrimaryRoute() inferred "served by the shared primary backend" from the mere presence of connection.profile. But pooled backends (a local named profile, or a per-profile remote override) also carry `profile` so their WebSocket URL mints against the right backend. Both descriptors looked the same, so ensureGatewayForProfile() took the shared-primary branch for a pooled profile and never dialed its socket — Desktop stayed on the default profile's socket even though the sidebar (REST) listed the right sessions. Regression from NousResearch#85665 (8340823). Tag only the true shared-primary descriptor with an explicit `sharedPrimary: true` marker in ensureBackend() and check that marker instead of `profile`. Covers both the local-pool and remote-override routes — the whole bug class, not one path. Test asserts both sides of the invariant: a { profile, sharedPrimary: true } descriptor activates the primary socket without dialing, and a pooled descriptor carrying { profile } dials its own exact WebSocket URL. Supersedes NousResearch#85750, NousResearch#85778, NousResearch#85932 Fixes NousResearch#85777 Co-authored-by: Tigrannnnnnn <122704900+Tigrannnnnnn@users.noreply.github.com> Co-authored-by: Don Tuttle <11698271+wdon@users.noreply.github.com> Co-authored-by: plcunha <145560011+plcunha@users.noreply.github.com>
…0.20.1 sharedPrimaryRoute() inferred "served by the shared primary backend" from the mere presence of connection.profile. But pooled backends (a local named profile, or a per-profile remote override) also carry `profile` so their WebSocket URL mints against the right backend. Both descriptors looked the same, so ensureGatewayForProfile() took the shared-primary branch for a pooled profile and never dialed its socket — Desktop stayed on the default profile's socket even though the sidebar (REST) listed the right sessions. Regression from NousResearch#85665 (d16e236). Tag only the true shared-primary descriptor with an explicit `sharedPrimary: true` marker in ensureBackend() and check that marker instead of `profile`. Covers both the local-pool and remote-override routes — the whole bug class, not one path. Test asserts both sides of the invariant: a { profile, sharedPrimary: true } descriptor activates the primary socket without dialing, and a pooled descriptor carrying { profile } dials its own exact WebSocket URL. Supersedes NousResearch#85750, NousResearch#85778, NousResearch#85932 Fixes NousResearch#85777 Co-authored-by: Tigrannnnnnn <122704900+Tigrannnnnnn@users.noreply.github.com> Co-authored-by: Don Tuttle <11698271+wdon@users.noreply.github.com> Co-authored-by: plcunha <145560011+plcunha@users.noreply.github.com>
PR: fix(desktop): per-profile remote overrides attach chat WS to the local primary
What
A Desktop profile configured with its own remote gateway override
(connection.json
profiles[name] = { mode: "remote", url, token })silently runs its chat on the local primary backend:
Regression from fix(desktop): profiles unusable over a global SSH remote — don't dial doomed per-profile sockets #85665 (d16e236), merged 2026-08-13.
Root cause
sharedPrimaryRoute()(apps/desktop/src/store/gateway.ts) decides a profileis "served by the shared primary" when the descriptor has a
.profilefield:But two routes produce a
.profile-tagged descriptor:ensureBackendreturns{ ...connection, profile: route.descriptorProfile }— the intended targetof the check.
spawnPoolBackendreturns{ ...remote, profile, ... }(electron/main.ts:8117) — the pool descriptoris tagged with the profile name too.
The check cannot distinguish them, so a per-profile remote override is
misclassified as shared-primary →
ensureGatewayForProfileactivates thePRIMARY (local) socket instead of dialing the profile's remote WS. The commit
message claims "per-profile remote overrides are untouched (pinned by test)" —
the test missed the
spawnPoolBackendreturn path.Fix
Tag only the true shared-primary descriptor with a dedicated marker and check
for that, not
.profile:electron/main.ts— primary route withdescriptorProfile:{ ...connection, profile: route.descriptorProfile, sharedPrimary: true }apps/desktop/src/store/gateway.ts—sharedPrimaryRoutechecksconn.sharedPrimary === true; pool descriptors never carry the marker.Covers both callers (
openGatewayForProfilepre-warm andensureGatewayForProfile) — the whole bug class, not just one path.Reproduction
while the sidebar (REST) lists remote sessions.
Test
sharedPrimaryRoute— descriptor{ profile: 'x' }→ false;{ profile: 'x', sharedPrimary: true }→ true (covers both route shapes).override's WS URL (assert the chat socket target, not just REST).
Related
Fixes #85777