Repository navigation
Description
Why this framing. #637 asked for an identity-model audit and was closed as feature-request intake. This is deliberately not that: no redesign, just a reachable, permanently-broken user state with no in-app recovery. Filing as the bug it is, per CONTRIBUTING.
What's broken
A host in the registry whose daemon has been replaced by one with a different serverId becomes permanently unusable, with no in-app way to fix it. The host stays listed, every connection attempt fails, and the only recovery is clearing browser storage — which is not discoverable and also destroys unrelated state.
Concretely: my registry kept pointing at a srv_… for a daemon that no longer existed anywhere on the machine, while the daemon actually reachable at that same endpoint had a different id. The stale entry survived reloads indefinitely.
Why it never recovers (verified on v0.10.2)
Two paths, and only one heals:
-
Placeholder ids heal.
packages/app/src/runtime/host-runtime.ts:1014— on an id mismatch, if the stored id is a placeholder (isPlaceholderServerId,local:-prefixed,:1427), it callsonReconcileServerIdand rekeys everything (reconcileServerId,:1716). -
Real ids do not. For a non-placeholder stored id the same branch closes the client and throws:
throw new Error(`Connection resolved to ${serverId}, expected ${this.host.serverId}.`);
Nothing removes, flags, or reconciles the profile. The next attempt takes the identical path.
Load-time cleanup has the same asymmetry — loadFromStorage (:1583) filters out placeholder-id profiles and re-persists, but a real-looking srv_… that exists nowhere is preserved verbatim, forever:
profiles = normalizedProfiles.filter((entry) => !isPlaceholderServerId(entry.serverId));So the gap is narrow and specific: a stored real serverId that no longer resolves has no reconciliation path and no eviction path.
Steps to reproduce
- Open the web UI against a daemon at a direct endpoint (e.g.
localhost:6767). A profile is stored with that daemon'sserverId. - Replace that daemon with a fresh one on the same endpoint, so it generates a new
serverId. Any of: a newPASEO_HOME, a fresh keypair, or — the case I hit — recreating the container/VM the daemon runs in while the published host port stays the same. - Reload the web UI.
Expected: the host reconciles to the new serverId (as it already does for placeholder ids), or is clearly reported as a different daemon with an offered action.
Actual: the host remains listed and never connects. The console repeats Connection resolved to <new>, expected <old>. Reloading does not help — the entry is re-read from storage unchanged. The only fix is DevTools ▸ Application ▸ Storage ▸ Clear site data, or deleting @paseo:daemon-registry by hand.
Worth noting "Empty Cache and Hard Reload" does not clear Local Storage, so the remedy a user would naturally try doesn't work — which is much of why this reads as "the app is broken" rather than "stale local state."
Why this is routine, not an edge case
Container and VM workflows hit step 2 as ordinary maintenance: recreating the VM regenerates the daemon identity while the published endpoint stays byte-identical. The client has no way to notice, because the endpoint it stored is the thing that didn't change.
Suggested direction (not a redesign)
Any one of these closes it; no preference:
- Treat a real-but-unresolvable stored id like a placeholder in the mismatch branch — reconcile to the observed
serverIdwhen the connection matches and the old id answers nowhere. - Or evict: if a probe resolves a different
serverIdon a stored connection, drop the stale profile and upsert the observed one.upsertHostConnectionInProfiles(packages/app/src/types/host-connection.ts:234) already matches profiles byserverIdor by connection equality, so the merge machinery exists. - Or at minimum surface it: show the host as "different daemon at this address" with a one-tap Forget host. Even without automatic reconciliation that removes the dead end.
Environment
- Paseo 0.10.2 (source-verified; hit on 0.9.1, and all paths cited above are unchanged between the two tags)
- Web (browser) surface; Chrome, macOS 15 arm64; direct TCP connection, relay disabled
GSA-TTS engineer. Investigation AI-assisted (OpenCode); source claims human-verified against v0.10.2.
Activity
paseo-bot commentedon Sep 30, 2026
Thanks for writing this up. I'm closing it because issues here are for bugs, and Paseo refusing a daemon whose server ID differs from the one it stored is deliberate: only placeholder IDs are reconciled. Having it adopt a recreated daemon at the same address is a workflow change; ideas get read in Discussions, and there's one about recovering a host after its daemon is recreated at #4132.
The stale entry can be removed without clearing site data: open that host in Settings and use Remove host, then add the endpoint again to register the new daemon.