fix(sublime/sessions): gate auto-spawn paths behind explicit reconnect
After a Sublime restart, restoring a Sessions project window would silently spawn the Rust bridge (SSH + ``session_helper`` push + handshake) before the user invoked any reconnect command. Two listeners triggered the spawn through ``execute_remote_exec_once``, which calls ``_persistent_bridge_for_host`` with ``allow_spawn=True`` by default: - ``SessionsPythonInterpreterStatusListener.on_activated_async``: on every Python view focus, if the cached interpreter version was missing, it scheduled ``_probe_active_python_version_task`` -> ``probe_interpreter_version`` -> ``execute_remote_exec_once``. Restored project windows always start with a cold version cache, so this fired for every restored ``.py`` view. - ``SessionsRemotePythonPipelineListener.on_activated_async``: gated on ``sessions_remote_python_auto_diagnostics_on_open`` (default false) but had the same shape — when opted-in it would auto-spawn on focus. Add the ``_workspace_runtime_connected`` gate that the other on-load / on-activated callbacks already use (sidebar placeholder hydrate, LSP workspace activation tracer, active-remote-view revalidate). The status bar still paints the cached interpreter label or the ``(…)`` ellipsis from project metadata, so the slot isn't blank — only the bridge probe is deferred until the user runs ``Sessions: Reconnect Current Workspace``. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -571,6 +571,14 @@ class SessionsRemotePythonPipelineListener(sublime_plugin.EventListener):
|
||||
window = window_fn() if callable(window_fn) else None
|
||||
if window is None:
|
||||
return
|
||||
# Same rationale as the version-probe listener: don't spawn the
|
||||
# bridge on a restored project window before the user has
|
||||
# explicitly reconnected.
|
||||
context = _root._workspace_context(
|
||||
window, SessionsSettings(), missing_detail_message=False
|
||||
)
|
||||
if context is None or not _root._workspace_runtime_connected(window, context):
|
||||
return
|
||||
view_id_fn = getattr(view, "id", None)
|
||||
view_id = view_id_fn() if callable(view_id_fn) else -1
|
||||
if view_id < 0:
|
||||
@@ -848,7 +856,11 @@ class SessionsPythonInterpreterStatusListener(sublime_plugin.EventListener):
|
||||
host_alias = context.recent_entry.host_alias
|
||||
cached = get_cached_version(host_alias, active_path)
|
||||
_set_active_python_status(view, format_status_label(active_path, cached))
|
||||
if cached is None:
|
||||
# Defer the version probe until the user has explicitly reconnected.
|
||||
# Restored project windows would otherwise spawn the bridge (SSH +
|
||||
# session_helper push) on focus, which the user reported as
|
||||
# unexpected auto-reconnect after Sublime restart.
|
||||
if cached is None and _root._workspace_runtime_connected(window, context):
|
||||
_root._run_in_background(
|
||||
_probe_active_python_version_task,
|
||||
view,
|
||||
|
||||
@@ -324,6 +324,7 @@ def test_python_interpreter_status_listener_schedules_probe_when_uncached(
|
||||
)
|
||||
view = _StatusView(window)
|
||||
monkeypatch.setattr(commands, "_workspace_context", lambda *a, **kw: _ctx())
|
||||
monkeypatch.setattr(commands, "_workspace_runtime_connected", lambda *a, **kw: True)
|
||||
|
||||
scheduled: List[Dict[str, Any]] = []
|
||||
|
||||
@@ -344,6 +345,49 @@ def test_python_interpreter_status_listener_schedules_probe_when_uncached(
|
||||
)
|
||||
|
||||
|
||||
def test_python_interpreter_status_listener_skips_probe_when_disconnected(
|
||||
monkeypatch,
|
||||
) -> None:
|
||||
"""Restored project window must not auto-spawn the bridge for the version probe.
|
||||
|
||||
The listener should still paint the cached/ellipsis status so the user
|
||||
sees the previously-known interpreter, but ``_run_in_background`` for
|
||||
the probe is gated on ``_workspace_runtime_connected``. Without this
|
||||
gate, opening Sublime + a Sessions project file silently spawns SSH +
|
||||
pushes ``session_helper`` before the user has explicitly reconnected.
|
||||
"""
|
||||
from sessions.python_interpreter_registry import invalidate_version_cache
|
||||
|
||||
invalidate_version_cache()
|
||||
listener = commands.SessionsPythonInterpreterStatusListener()
|
||||
window = FakeWindow(
|
||||
project_data={
|
||||
"settings": {
|
||||
"sessions_active_python_interpreter": "/srv/ws/.venv/bin/python"
|
||||
}
|
||||
}
|
||||
)
|
||||
view = _StatusView(window)
|
||||
monkeypatch.setattr(commands, "_workspace_context", lambda *a, **kw: _ctx())
|
||||
monkeypatch.setattr(
|
||||
commands, "_workspace_runtime_connected", lambda *a, **kw: False
|
||||
)
|
||||
|
||||
scheduled: List[Dict[str, Any]] = []
|
||||
monkeypatch.setattr(
|
||||
commands,
|
||||
"_run_in_background",
|
||||
lambda *a, **kw: scheduled.append((a, kw)),
|
||||
)
|
||||
|
||||
listener.on_activated_async(view)
|
||||
# Status still paints with ellipsis so the slot is not blank.
|
||||
assert view.statuses["sessions_active_python"] == "Python: ws (…)"
|
||||
# But no probe is scheduled — bridge stays untouched until the user
|
||||
# runs ``Sessions: Reconnect Current Workspace``.
|
||||
assert scheduled == []
|
||||
|
||||
|
||||
def test_apply_active_python_change_invalidates_version_cache(monkeypatch) -> None:
|
||||
"""Selection-change path drops cached versions for the affected host."""
|
||||
from sessions.python_interpreter_registry import (
|
||||
@@ -611,6 +655,7 @@ def test_status_listener_handles_short_timeout_ms_zero_passthrough(monkeypatch)
|
||||
)
|
||||
view = _StatusView(window)
|
||||
monkeypatch.setattr(commands, "_workspace_context", lambda *a, **kw: _ctx())
|
||||
monkeypatch.setattr(commands, "_workspace_runtime_connected", lambda *a, **kw: True)
|
||||
|
||||
# The probe (with timeout_ms=0) returns nothing → no repaint.
|
||||
monkeypatch.setattr(
|
||||
|
||||
Reference in New Issue
Block a user