diff --git a/src/panopticon/terminal/console.py b/src/panopticon/terminal/console.py index dc6d2e31..3c65c13c 100644 --- a/src/panopticon/terminal/console.py +++ b/src/panopticon/terminal/console.py @@ -21,6 +21,7 @@ from __future__ import annotations +import socket as socket_module import subprocess import sys import tempfile @@ -69,14 +70,21 @@ def switch_to( host: str | None = None, switch_file: Path, detach: Callable[[], None] = _tmux_detach, + local_host: Callable[[], str] = socket_module.gethostname, ) -> None: """The dashboard's `t` hook, run inside its tmux session: record the picked ``session`` for the supervisor, then detach this client so the supervisor attaches the task. The dashboard process keeps running (detached), so returning to it shows the same live view. - When ``host`` is set the switch-file carries ``\\t`` so the - supervisor can ssh-wrap the attach; a plain ```` (no tab) means local. + ``host`` is the claiming runner's registered hostname (ADR 0013 §6) — populated even for the + ordinary local runner. It only means "remote" when it differs from this machine's own + hostname (``local_host``, injectable for tests); otherwise it's treated as local so `t` never + ssh-wraps a same-machine attach. When genuinely remote, the switch-file carries + ``\\t`` so the supervisor can ssh-wrap the attach; a plain ```` (no + tab) means local. """ + if host == local_host(): + host = None switch_file.write_text(f"{host}\t{session}" if host else session) detach() diff --git a/tests/test_console.py b/tests/test_console.py index 445522cd..7f4da19f 100644 --- a/tests/test_console.py +++ b/tests/test_console.py @@ -79,11 +79,28 @@ def test_switch_to_with_remote_host_encodes_host_and_session(tmp_path: Path) -> # parse it and pass host= to attach_command for the ssh-wrapped attach. switch = tmp_path / "switch" - switch_to("panopticon-t1", host="box.example.com", switch_file=switch, detach=lambda: None) + switch_to( + "panopticon-t1", host="box.example.com", switch_file=switch, detach=lambda: None, + local_host=lambda: "this-machine", + ) assert switch.read_text() == "box.example.com\tpanopticon-t1" +def test_switch_to_with_own_hostname_is_treated_as_local(tmp_path: Path) -> None: + # The ordinary local runner registers its own hostname too (ADR 0013 §6), but that must not + # ssh-wrap a same-machine attach: when host == local_host() it's dropped, so the switch-file + # carries a plain session (no tab) just like the no-host case. + switch = tmp_path / "switch" + + switch_to( + "panopticon-t1", host="this-machine", switch_file=switch, detach=lambda: None, + local_host=lambda: "this-machine", + ) + + assert switch.read_text() == "panopticon-t1" + + def test_supervisor_parses_remote_host_from_switch_file(tmp_path: Path) -> None: # run_console_local's attach() closure parses "\t" from the switch-file and # passes host= to attach_command; a plain session (no tab) means local (host=None).