Note
Grok responding on behalf of Julius.
Triage
Thanks for the write-up, @vitalyiegorov. This is a real bug, and it's separate from #14979.
What's wrong: EnvironmentRegistry.setCompatibility records a protocol rejection as if the user had switched the environment off. When a saved environment is on, it sets enabled: false and calls registrations.setEnabled(id, false), which writes to the persisted switched-off list. unsupportedReason lives only in memory. When a later compatible health check calls setCompatibility(id, null), only the reason is cleared: enabled stays false and nothing reconnects. After a relaunch the reason is gone, so the row shows Off with the Switched off tooltip. That's the same thing @michft described on #14979 once both hosts were on the same nightly.
The merged #11990 added this to stop an incompatible server from sitting in a retry loop. Holding the connection off while the host is old makes sense, but it shouldn't become the saved state once the host is compatible again. The in-app update flow already recovers, because outdatedHostUpdate clears the block and then calls setEnabled(true). A host updated on its own (launchd, systemd, or a manual upgrade) never goes through that path, so its threads stay hidden until someone notices the switch.
Intended behavior: compatibility should be runtime state, not a saved setting.
- While the protocol doesn't match, keep
unsupportedReason, disconnect, and show Client not supported with the switch disabled. That should apply to both relay discovery and a socket preflight rejection. - Leave
enabledand the persisted switched-off list untouched for that block, so an environment the user turned off stays off. - When a fresh check clears the block, reconnect if
enabledis still true. Discovery should still ignore a replayed health result, so an older relay snapshot can't clear a newer socket rejection. - Only reconnect when the block is cleared, so a host that's still on an old build doesn't loop.
Fix scope: the packages/client-runtime registry, plus the registry tests that currently expect the opposite. One trap: setEnabled(true) returns early when enabled is already true. Once the block stops flipping enabled, setCompatibility(null) has to connect an enabled environment itself, or the in-app update flow stops resuming.
Rows that were already saved as switched off will stay off. They have no stored reason, so they can't be told apart from a real user toggle, and on those machines the workaround is still to switch each environment back on once.
#14979 covers the dark-mode switch contrast, which the open PR #14984 addresses. This issue covers environments that stay off after the host becomes compatible again. A maintainer will decide on the fix direction.