fix: DPLAN-0241 round 4 — R6 extra_args threaded through ALL launch paths (user flags survive resume/takeover/continue/dead-window/headless), R7 auto-namer stamps --name branch-shortid on every launch (flag live-verified 2.1.209, user -n/--name wins), new-over-all aborts on failed daemon stop, honest close-all hint, exit/q/quit in all menus. op:kill per-job stop found in daemon socket protocol — documented, not shipped (undocumented internal). 1048 hooks tests green (102 session_boot, 11 CLI contract).
This commit is contained in:
@@ -9,6 +9,28 @@ PyPI version — not the changelog header.
|
||||
|
||||
---
|
||||
|
||||
## [2026-07-14]
|
||||
|
||||
### Fixed
|
||||
|
||||
- **DPLAN-0241 round 4 (night shift): user flags survive every launch path, and
|
||||
every session is born with an honest name.** R6 — the bug behind Patrick's
|
||||
approve-everything chat: the boot menu suppressed its bypass defaults when the
|
||||
user passed `--permission-mode` himself, but only the fresh-launch path threaded
|
||||
the user's flags into the exec — resume, takeover, continue, and dead-window
|
||||
paths all launched flagless. `extra_args` now threads through ALL launch paths
|
||||
(headless `-p` included). R7 — auto-namer: every launch is stamped
|
||||
`--name <branch>-<short-session-id>` (flag live-verified on claude 2.1.209; a
|
||||
user-passed `-n/--name` wins), so made-up auto-names can no longer hide which
|
||||
chat is which. Plus four drill nits: new-over-all ABORTS if the daemon stop
|
||||
fails (one brain even in failure paths), close-all's failure hint no longer
|
||||
recommends the mechanism that just failed, `exit`/`q`/`quit` quietly leave every
|
||||
menu, session rows stay rich (PID, kind, name, age). Surgical-stop probe:
|
||||
`op:kill` exists in the daemon's Unix-socket control protocol (per-job bg stop,
|
||||
8-char sessionId prefix, no auth) — documented in DPLAN-0241, deliberately NOT
|
||||
shipped: undocumented internal protocol. 1048 hooks tests green (102
|
||||
session_boot, 11 real-binary CLI contract).
|
||||
|
||||
## [2026-07-13]
|
||||
|
||||
### Fixed
|
||||
|
||||
@@ -225,7 +225,9 @@ def _stop_session(session: dict, claude_bin: str) -> str:
|
||||
return f"PID {pid}: no action"
|
||||
|
||||
|
||||
def _resume_session(session: dict, branch: str, claude_bin: str, defaults: list[str]) -> dict:
|
||||
def _resume_session(
|
||||
session: dict, branch: str, claude_bin: str, defaults: list[str], extra_args: list[str] | None = None
|
||||
) -> dict:
|
||||
"""Resume a session — right mechanism per kind.
|
||||
|
||||
bg: takeover (daemon stop + --resume in tmux). Never opens agents view.
|
||||
@@ -234,9 +236,10 @@ def _resume_session(session: dict, branch: str, claude_bin: str, defaults: list[
|
||||
"""
|
||||
pid = session.get("pid")
|
||||
kind = session.get("kind", "unknown")
|
||||
ea = list(extra_args or [])
|
||||
|
||||
if kind in ("bg", "background"):
|
||||
return _takeover_bg(session, branch, claude_bin, defaults)
|
||||
return _takeover_bg(session, branch, claude_bin, defaults, extra_args)
|
||||
|
||||
tmux_session = _find_tmux_session_for_pid(pid) if pid else None
|
||||
if tmux_session:
|
||||
@@ -245,7 +248,9 @@ def _resume_session(session: dict, branch: str, claude_bin: str, defaults: list[
|
||||
return {"exit_code": 0, "action": "attached", "tmux_session": tmux_session}
|
||||
|
||||
logger.info("[SESSION_BOOT] Continuing dead-window session via --continue")
|
||||
return _exec_in_tmux(branch, "", claude_bin, [claude_bin] + defaults + ["--continue"])
|
||||
sid = session.get("sessionId", "")
|
||||
nf = _name_flag(branch, sid, extra_args)
|
||||
return _exec_in_tmux(branch, "", claude_bin, [claude_bin] + defaults + ["--continue"] + ea + nf)
|
||||
|
||||
|
||||
def _make_session_name(branch: str, session_id: str = "") -> str:
|
||||
@@ -256,6 +261,13 @@ def _make_session_name(branch: str, session_id: str = "") -> str:
|
||||
return branch
|
||||
|
||||
|
||||
def _name_flag(branch: str, session_id: str = "", extra_args: list[str] | None = None) -> list[str]:
|
||||
"""Build --name args for session stamping, unless user already provided one."""
|
||||
if extra_args and ("-n" in extra_args or "--name" in extra_args):
|
||||
return []
|
||||
return ["--name", _make_session_name(branch, session_id)]
|
||||
|
||||
|
||||
def _exec_in_tmux(branch: str, session_id: str, claude_bin: str, claude_cmd: list[str]) -> dict:
|
||||
"""Exec a claude command inside a new tmux session."""
|
||||
session_name = _make_session_name(branch, session_id)
|
||||
@@ -291,7 +303,7 @@ def boot(cwd: str | None = None, extra_args: list[str] | None = None) -> dict:
|
||||
|
||||
if os.environ.get("TMUX"):
|
||||
logger.info("[SESSION_BOOT] Already inside tmux — running claude directly")
|
||||
claude_cmd = [claude_bin] + defaults
|
||||
claude_cmd = [claude_bin] + defaults + _name_flag(branch, extra_args=extra_args)
|
||||
if extra_args:
|
||||
claude_cmd.extend(extra_args)
|
||||
os.execvp(claude_bin, claude_cmd)
|
||||
@@ -375,7 +387,9 @@ def _daemon_stop(claude_bin: str, branch: str, pid: int | None) -> dict:
|
||||
return {"ok": True}
|
||||
|
||||
|
||||
def _takeover_bg(session: dict, branch: str, claude_bin: str, defaults: list[str]) -> dict:
|
||||
def _takeover_bg(
|
||||
session: dict, branch: str, claude_bin: str, defaults: list[str], extra_args: list[str] | None = None
|
||||
) -> dict:
|
||||
"""Take over a bg session: daemon stop --any, poll, then --resume in tmux.
|
||||
|
||||
Checks blast radius first (other branches' bg sessions). On daemon stop
|
||||
@@ -384,6 +398,8 @@ def _takeover_bg(session: dict, branch: str, claude_bin: str, defaults: list[str
|
||||
"""
|
||||
session_id = session.get("sessionId", "")
|
||||
pid = session.get("pid")
|
||||
ea = list(extra_args or [])
|
||||
nf = _name_flag(branch, session_id, extra_args)
|
||||
|
||||
stop_result = _daemon_stop(claude_bin, branch, pid)
|
||||
if not stop_result["ok"]:
|
||||
@@ -391,10 +407,12 @@ def _takeover_bg(session: dict, branch: str, claude_bin: str, defaults: list[str
|
||||
|
||||
if session_id:
|
||||
logger.info("[SESSION_BOOT] Resuming session %s after takeover", session_id[:8])
|
||||
return _exec_in_tmux(branch, session_id, claude_bin, [claude_bin] + defaults + ["--resume", session_id])
|
||||
return _exec_in_tmux(
|
||||
branch, session_id, claude_bin, [claude_bin] + defaults + ["--resume", session_id] + ea + nf
|
||||
)
|
||||
|
||||
logger.info("[SESSION_BOOT] No sessionId for takeover — continuing last")
|
||||
return _exec_in_tmux(branch, "", claude_bin, [claude_bin] + defaults + ["--continue"])
|
||||
return _exec_in_tmux(branch, "", claude_bin, [claude_bin] + defaults + ["--continue"] + ea + nf)
|
||||
|
||||
|
||||
def _is_session_file_present(pid: int | None) -> bool:
|
||||
@@ -430,7 +448,7 @@ def _menu_live(
|
||||
choice = _read_choice()
|
||||
|
||||
if choice in ("", "r"):
|
||||
return _resume_session(session, branch, claude_bin, defaults)
|
||||
return _resume_session(session, branch, claude_bin, defaults, extra_args)
|
||||
elif choice == "n":
|
||||
if is_bg:
|
||||
stop = _daemon_stop(claude_bin, branch, session.get("pid"))
|
||||
@@ -449,6 +467,8 @@ def _menu_live(
|
||||
result = _stop_session(session, claude_bin)
|
||||
sys.stderr.write(f" {result}\n")
|
||||
return {"exit_code": 0, "action": "closed"}
|
||||
elif choice in ("exit", "q", "quit"):
|
||||
return {"exit_code": 0, "action": "quit"}
|
||||
else:
|
||||
sys.stderr.write(" Unknown choice. Exiting.\n")
|
||||
return {"exit_code": 1, "error": "unknown choice"}
|
||||
@@ -468,10 +488,13 @@ def _menu_live(
|
||||
if choice == "n":
|
||||
return _new_over_all(live, branch, claude_bin, defaults, extra_args)
|
||||
|
||||
if choice in ("exit", "q", "quit"):
|
||||
return {"exit_code": 0, "action": "quit"}
|
||||
|
||||
try:
|
||||
idx = int(choice) - 1
|
||||
if 0 <= idx < len(live):
|
||||
return _resume_session(live[idx], branch, claude_bin, defaults)
|
||||
return _resume_session(live[idx], branch, claude_bin, defaults, extra_args)
|
||||
except (ValueError, IndexError):
|
||||
logger.info("[SESSION_BOOT] Invalid menu choice: %r", choice)
|
||||
|
||||
@@ -492,7 +515,7 @@ def _close_all(live: list[dict], branch: str, claude_bin: str) -> dict:
|
||||
sys.stderr.write(f" Stopped {len(bg)} bg session(s) via daemon stop.\n")
|
||||
else:
|
||||
for s in bg:
|
||||
sys.stderr.write(f" PID {s.get('pid')}: bg session remains (use Enter to take over)\n")
|
||||
sys.stderr.write(f" PID {s.get('pid')}: bg session remains — daemon stop failed\n")
|
||||
return {"exit_code": 0, "action": "closed_all"}
|
||||
|
||||
|
||||
@@ -512,7 +535,8 @@ def _new_over_all(
|
||||
if bg:
|
||||
stop = _daemon_stop(claude_bin, branch, bg[0].get("pid"))
|
||||
if not stop["ok"]:
|
||||
sys.stderr.write(f" {len(bg)} bg session(s) remain — starting new chat anyway\n")
|
||||
sys.stderr.write(" Cannot start new — bg session(s) still running.\n")
|
||||
return {"exit_code": 1, "error": "daemon stop failed, aborting to preserve one-brain"}
|
||||
return _start_fresh(branch, claude_bin, defaults, extra_args)
|
||||
|
||||
|
||||
@@ -531,9 +555,13 @@ def _menu_no_live(
|
||||
|
||||
if choice in ("", "r"):
|
||||
logger.info("[SESSION_BOOT] Continuing last chat via --continue")
|
||||
return _exec_in_tmux(branch, "", claude_bin, [claude_bin] + defaults + ["--continue"])
|
||||
nf = _name_flag(branch, extra_args=extra_args)
|
||||
cmd = [claude_bin] + defaults + ["--continue"] + list(extra_args or []) + nf
|
||||
return _exec_in_tmux(branch, "", claude_bin, cmd)
|
||||
elif choice == "n":
|
||||
return _start_fresh(branch, claude_bin, defaults, extra_args)
|
||||
elif choice in ("exit", "q", "quit"):
|
||||
return {"exit_code": 0, "action": "quit"}
|
||||
else:
|
||||
sys.stderr.write(" Unknown choice. Exiting.\n")
|
||||
return {"exit_code": 1, "error": "unknown choice"}
|
||||
@@ -552,7 +580,7 @@ def _start_fresh(
|
||||
logger.info("[SESSION_BOOT] Killing stale tmux session '%s'", session_name)
|
||||
subprocess.run(["tmux", "kill-session", "-t", session_name], check=False)
|
||||
|
||||
claude_cmd = [claude_bin] + defaults
|
||||
claude_cmd = [claude_bin] + defaults + _name_flag(branch, extra_args=extra_args)
|
||||
if extra_args:
|
||||
claude_cmd.extend(extra_args)
|
||||
|
||||
|
||||
@@ -54,6 +54,10 @@ class TestClaudeMainFlags:
|
||||
h = _get_main_help()
|
||||
assert "-p" in h or "--print" in h
|
||||
|
||||
def test_name_flag(self):
|
||||
h = _get_main_help()
|
||||
assert "--name" in h or "-n" in h
|
||||
|
||||
|
||||
@_SKIP
|
||||
class TestClaudeAgentsFlags:
|
||||
|
||||
@@ -118,6 +118,28 @@ class TestMakeSessionName:
|
||||
assert session_boot._make_session_name("hooks", "") == "hooks"
|
||||
|
||||
|
||||
class TestNameFlag:
|
||||
def test_branch_only(self):
|
||||
result = session_boot._name_flag("hooks")
|
||||
assert result == ["--name", "hooks"]
|
||||
|
||||
def test_branch_with_session_id(self):
|
||||
result = session_boot._name_flag("hooks", "abcdef1234")
|
||||
assert result == ["--name", "hooks-abcdef12"]
|
||||
|
||||
def test_skips_when_user_provides_name(self):
|
||||
result = session_boot._name_flag("hooks", "", ["--name", "myname"])
|
||||
assert result == []
|
||||
|
||||
def test_skips_when_user_provides_n(self):
|
||||
result = session_boot._name_flag("hooks", "", ["-n", "myname"])
|
||||
assert result == []
|
||||
|
||||
def test_adds_when_extra_args_no_name(self):
|
||||
result = session_boot._name_flag("hooks", "", ["--verbose"])
|
||||
assert result == ["--name", "hooks"]
|
||||
|
||||
|
||||
class TestSessionLabel:
|
||||
def test_formats_label(self):
|
||||
session = {"pid": 1234, "sessionId": "abcdef1234", "kind": "interactive"}
|
||||
@@ -820,3 +842,260 @@ class TestIsSessionFilePresent:
|
||||
|
||||
def test_none_pid(self):
|
||||
assert session_boot._is_session_file_present(None) is False
|
||||
|
||||
|
||||
class TestExtraArgsThreading:
|
||||
"""R6: extra_args must reach every launch path, not just _start_fresh."""
|
||||
|
||||
def test_resume_dead_window_threads_extra_args(self, tmp_path):
|
||||
session = {"pid": 1234, "sessionId": "abc", "cwd": str(tmp_path), "kind": "interactive"}
|
||||
with (
|
||||
patch.object(session_boot, "_find_tmux_session_for_pid", return_value=None),
|
||||
patch.object(session_boot, "_tmux_session_exists", return_value=False),
|
||||
patch(f"{_MOD}.os.execvp") as mock_exec,
|
||||
):
|
||||
session_boot._resume_session(session, "hooks", "/usr/local/bin/claude", [], ["--permission-mode", "plan"])
|
||||
cmd = mock_exec.call_args[0][1]
|
||||
assert "--continue" in cmd
|
||||
assert "--permission-mode" in cmd
|
||||
assert "plan" in cmd
|
||||
|
||||
def test_takeover_bg_threads_extra_args(self, tmp_path):
|
||||
session = {"pid": 1234, "sessionId": "abc-uuid", "cwd": str(tmp_path), "kind": "bg"}
|
||||
with (
|
||||
patch.object(session_boot, "_daemon_stop", return_value={"ok": True}),
|
||||
patch.object(session_boot, "_tmux_session_exists", return_value=False),
|
||||
patch(f"{_MOD}.os.execvp") as mock_exec,
|
||||
):
|
||||
session_boot._takeover_bg(session, "hooks", "/usr/local/bin/claude", [], ["--permission-mode", "plan"])
|
||||
cmd = mock_exec.call_args[0][1]
|
||||
assert "--resume" in cmd
|
||||
assert "--permission-mode" in cmd
|
||||
assert "plan" in cmd
|
||||
|
||||
def test_takeover_bg_continue_threads_extra_args(self, tmp_path):
|
||||
session = {"pid": 1234, "sessionId": "", "cwd": str(tmp_path), "kind": "bg"}
|
||||
with (
|
||||
patch.object(session_boot, "_daemon_stop", return_value={"ok": True}),
|
||||
patch.object(session_boot, "_tmux_session_exists", return_value=False),
|
||||
patch(f"{_MOD}.os.execvp") as mock_exec,
|
||||
):
|
||||
session_boot._takeover_bg(session, "hooks", "/usr/local/bin/claude", [], ["--permission-mode", "plan"])
|
||||
cmd = mock_exec.call_args[0][1]
|
||||
assert "--continue" in cmd
|
||||
assert "--permission-mode" in cmd
|
||||
|
||||
def test_no_live_continue_threads_extra_args(self, tmp_path):
|
||||
with (
|
||||
patch.dict("os.environ", {}, clear=True),
|
||||
patch.object(session_boot, "_resolve_claude_binary", return_value="/usr/local/bin/claude"),
|
||||
patch.object(session_boot, "_find_tmux", return_value="/usr/bin/tmux"),
|
||||
patch.object(session_boot, "_find_live_sessions", return_value=[]),
|
||||
patch.object(session_boot, "_read_choice", return_value=""),
|
||||
patch(f"{_MOD}.os.execvp") as mock_exec,
|
||||
):
|
||||
session_boot.boot(cwd=str(tmp_path), extra_args=["--permission-mode", "plan"])
|
||||
cmd = mock_exec.call_args[0][1]
|
||||
assert "--continue" in cmd
|
||||
assert "--permission-mode" in cmd
|
||||
assert "plan" in cmd
|
||||
|
||||
def test_live_bg_enter_threads_extra_args(self, tmp_path):
|
||||
live = [{"pid": 1234, "sessionId": "abc", "cwd": str(tmp_path), "kind": "bg"}]
|
||||
with (
|
||||
patch.dict("os.environ", {}, clear=True),
|
||||
patch.object(session_boot, "_resolve_claude_binary", return_value="/usr/local/bin/claude"),
|
||||
patch.object(session_boot, "_find_tmux", return_value="/usr/bin/tmux"),
|
||||
patch.object(session_boot, "_find_live_sessions", return_value=live),
|
||||
patch.object(session_boot, "_read_choice", return_value=""),
|
||||
patch.object(session_boot, "_daemon_stop", return_value={"ok": True}),
|
||||
patch.object(session_boot, "_tmux_session_exists", return_value=False),
|
||||
patch(f"{_MOD}.os.execvp") as mock_exec,
|
||||
):
|
||||
session_boot.boot(cwd=str(tmp_path), extra_args=["--permission-mode", "plan"])
|
||||
cmd = mock_exec.call_args[0][1]
|
||||
assert "--resume" in cmd
|
||||
assert "--permission-mode" in cmd
|
||||
assert "plan" in cmd
|
||||
|
||||
def test_multi_session_pick_threads_extra_args(self, tmp_path):
|
||||
live = [
|
||||
{"pid": 1234, "sessionId": "abc", "cwd": str(tmp_path), "kind": "interactive"},
|
||||
{"pid": 5678, "sessionId": "def", "cwd": str(tmp_path), "kind": "interactive"},
|
||||
]
|
||||
with (
|
||||
patch.dict("os.environ", {}, clear=True),
|
||||
patch.object(session_boot, "_resolve_claude_binary", return_value="/usr/local/bin/claude"),
|
||||
patch.object(session_boot, "_find_tmux", return_value="/usr/bin/tmux"),
|
||||
patch.object(session_boot, "_find_live_sessions", return_value=live),
|
||||
patch.object(session_boot, "_read_choice", return_value="1"),
|
||||
patch.object(session_boot, "_find_tmux_session_for_pid", return_value=None),
|
||||
patch.object(session_boot, "_tmux_session_exists", return_value=False),
|
||||
patch(f"{_MOD}.os.execvp") as mock_exec,
|
||||
):
|
||||
session_boot.boot(cwd=str(tmp_path), extra_args=["--permission-mode", "plan"])
|
||||
cmd = mock_exec.call_args[0][1]
|
||||
assert "--continue" in cmd
|
||||
assert "--permission-mode" in cmd
|
||||
assert "plan" in cmd
|
||||
|
||||
|
||||
class TestNewOverAllAbort:
|
||||
def test_aborts_on_daemon_stop_failure(self, tmp_path):
|
||||
live = [
|
||||
{"pid": 1234, "sessionId": "abc", "cwd": str(tmp_path), "kind": "interactive"},
|
||||
{"pid": 5678, "sessionId": "def", "cwd": str(tmp_path), "kind": "bg"},
|
||||
]
|
||||
with (
|
||||
patch.dict("os.environ", {}, clear=True),
|
||||
patch.object(session_boot, "_resolve_claude_binary", return_value="/usr/local/bin/claude"),
|
||||
patch.object(session_boot, "_find_tmux", return_value="/usr/bin/tmux"),
|
||||
patch.object(session_boot, "_find_live_sessions", return_value=live),
|
||||
patch.object(session_boot, "_read_choice", return_value="n"),
|
||||
patch.object(session_boot, "_stop_session", return_value="stopped"),
|
||||
patch.object(session_boot, "_daemon_stop", return_value={"ok": False, "error": "failed"}),
|
||||
patch(f"{_MOD}.os.execvp") as mock_exec,
|
||||
):
|
||||
result = session_boot.boot(cwd=str(tmp_path))
|
||||
assert result["exit_code"] == 1
|
||||
assert "one-brain" in result["error"]
|
||||
mock_exec.assert_not_called()
|
||||
|
||||
|
||||
class TestMenuQuit:
|
||||
def test_single_session_q_quits(self, tmp_path):
|
||||
live = [{"pid": 1234, "sessionId": "abc", "cwd": str(tmp_path), "kind": "interactive"}]
|
||||
with (
|
||||
patch.dict("os.environ", {}, clear=True),
|
||||
patch.object(session_boot, "_resolve_claude_binary", return_value="/usr/local/bin/claude"),
|
||||
patch.object(session_boot, "_find_tmux", return_value="/usr/bin/tmux"),
|
||||
patch.object(session_boot, "_find_live_sessions", return_value=live),
|
||||
patch.object(session_boot, "_read_choice", return_value="q"),
|
||||
):
|
||||
result = session_boot.boot(cwd=str(tmp_path))
|
||||
assert result["exit_code"] == 0
|
||||
assert result["action"] == "quit"
|
||||
|
||||
def test_single_session_exit_quits(self, tmp_path):
|
||||
live = [{"pid": 1234, "sessionId": "abc", "cwd": str(tmp_path), "kind": "interactive"}]
|
||||
with (
|
||||
patch.dict("os.environ", {}, clear=True),
|
||||
patch.object(session_boot, "_resolve_claude_binary", return_value="/usr/local/bin/claude"),
|
||||
patch.object(session_boot, "_find_tmux", return_value="/usr/bin/tmux"),
|
||||
patch.object(session_boot, "_find_live_sessions", return_value=live),
|
||||
patch.object(session_boot, "_read_choice", return_value="exit"),
|
||||
):
|
||||
result = session_boot.boot(cwd=str(tmp_path))
|
||||
assert result["action"] == "quit"
|
||||
|
||||
def test_multi_session_quit_quits(self, tmp_path):
|
||||
live = [
|
||||
{"pid": 1234, "sessionId": "abc", "cwd": str(tmp_path), "kind": "interactive"},
|
||||
{"pid": 5678, "sessionId": "def", "cwd": str(tmp_path), "kind": "interactive"},
|
||||
]
|
||||
with (
|
||||
patch.dict("os.environ", {}, clear=True),
|
||||
patch.object(session_boot, "_resolve_claude_binary", return_value="/usr/local/bin/claude"),
|
||||
patch.object(session_boot, "_find_tmux", return_value="/usr/bin/tmux"),
|
||||
patch.object(session_boot, "_find_live_sessions", return_value=live),
|
||||
patch.object(session_boot, "_read_choice", return_value="quit"),
|
||||
):
|
||||
result = session_boot.boot(cwd=str(tmp_path))
|
||||
assert result["action"] == "quit"
|
||||
|
||||
def test_no_live_q_quits(self, tmp_path):
|
||||
with (
|
||||
patch.dict("os.environ", {}, clear=True),
|
||||
patch.object(session_boot, "_resolve_claude_binary", return_value="/usr/local/bin/claude"),
|
||||
patch.object(session_boot, "_find_tmux", return_value="/usr/bin/tmux"),
|
||||
patch.object(session_boot, "_find_live_sessions", return_value=[]),
|
||||
patch.object(session_boot, "_read_choice", return_value="q"),
|
||||
):
|
||||
result = session_boot.boot(cwd=str(tmp_path))
|
||||
assert result["action"] == "quit"
|
||||
|
||||
|
||||
class TestAutoNamer:
|
||||
"""R7: --name flag stamped on every launch for self-identifying sessions."""
|
||||
|
||||
def test_fresh_start_gets_name(self, tmp_path):
|
||||
with (
|
||||
patch.dict("os.environ", {}, clear=True),
|
||||
patch.object(session_boot, "_resolve_claude_binary", return_value="/usr/local/bin/claude"),
|
||||
patch.object(session_boot, "_find_tmux", return_value="/usr/bin/tmux"),
|
||||
patch.object(session_boot, "_find_live_sessions", return_value=[]),
|
||||
patch.object(session_boot, "_read_choice", return_value="n"),
|
||||
patch.object(session_boot, "_tmux_session_exists", return_value=False),
|
||||
patch(f"{_MOD}.os.execvp") as mock_exec,
|
||||
):
|
||||
session_boot.boot(cwd=str(tmp_path))
|
||||
cmd = mock_exec.call_args[0][1]
|
||||
assert "--name" in cmd
|
||||
branch = tmp_path.name
|
||||
idx = cmd.index("--name")
|
||||
assert cmd[idx + 1] == branch
|
||||
|
||||
def test_takeover_gets_name_with_session_id(self, tmp_path):
|
||||
session = {"pid": 1234, "sessionId": "abc12345-full-uuid", "cwd": str(tmp_path), "kind": "bg"}
|
||||
with (
|
||||
patch.object(session_boot, "_daemon_stop", return_value={"ok": True}),
|
||||
patch.object(session_boot, "_tmux_session_exists", return_value=False),
|
||||
patch(f"{_MOD}.os.execvp") as mock_exec,
|
||||
):
|
||||
session_boot._takeover_bg(session, "hooks", "/usr/local/bin/claude", [])
|
||||
cmd = mock_exec.call_args[0][1]
|
||||
assert "--name" in cmd
|
||||
idx = cmd.index("--name")
|
||||
assert cmd[idx + 1] == "hooks-abc12345"
|
||||
|
||||
def test_in_tmux_gets_name(self, tmp_path):
|
||||
with (
|
||||
patch.dict("os.environ", {"TMUX": "/tmp/tmux-1000/default,123,0"}),
|
||||
patch.object(session_boot, "_resolve_claude_binary", return_value="/usr/local/bin/claude"),
|
||||
patch(f"{_MOD}.os.execvp") as mock_exec,
|
||||
):
|
||||
session_boot.boot(cwd=str(tmp_path))
|
||||
cmd = mock_exec.call_args[0][1]
|
||||
assert "--name" in cmd
|
||||
|
||||
def test_no_live_continue_gets_name(self, tmp_path):
|
||||
with (
|
||||
patch.dict("os.environ", {}, clear=True),
|
||||
patch.object(session_boot, "_resolve_claude_binary", return_value="/usr/local/bin/claude"),
|
||||
patch.object(session_boot, "_find_tmux", return_value="/usr/bin/tmux"),
|
||||
patch.object(session_boot, "_find_live_sessions", return_value=[]),
|
||||
patch.object(session_boot, "_read_choice", return_value=""),
|
||||
patch(f"{_MOD}.os.execvp") as mock_exec,
|
||||
):
|
||||
session_boot.boot(cwd=str(tmp_path))
|
||||
cmd = mock_exec.call_args[0][1]
|
||||
assert "--name" in cmd
|
||||
|
||||
def test_user_name_not_overridden(self, tmp_path):
|
||||
with (
|
||||
patch.dict("os.environ", {}, clear=True),
|
||||
patch.object(session_boot, "_resolve_claude_binary", return_value="/usr/local/bin/claude"),
|
||||
patch.object(session_boot, "_find_tmux", return_value="/usr/bin/tmux"),
|
||||
patch.object(session_boot, "_find_live_sessions", return_value=[]),
|
||||
patch.object(session_boot, "_read_choice", return_value="n"),
|
||||
patch.object(session_boot, "_tmux_session_exists", return_value=False),
|
||||
patch(f"{_MOD}.os.execvp") as mock_exec,
|
||||
):
|
||||
session_boot.boot(cwd=str(tmp_path), extra_args=["--name", "myname"])
|
||||
cmd = mock_exec.call_args[0][1]
|
||||
assert cmd.count("--name") == 1
|
||||
idx = cmd.index("--name")
|
||||
assert cmd[idx + 1] == "myname"
|
||||
|
||||
def test_dead_window_resume_gets_name_with_session_id(self, tmp_path):
|
||||
session = {"pid": 1234, "sessionId": "abc12345-uuid", "cwd": str(tmp_path), "kind": "interactive"}
|
||||
with (
|
||||
patch.object(session_boot, "_find_tmux_session_for_pid", return_value=None),
|
||||
patch.object(session_boot, "_tmux_session_exists", return_value=False),
|
||||
patch(f"{_MOD}.os.execvp") as mock_exec,
|
||||
):
|
||||
session_boot._resume_session(session, "hooks", "/usr/local/bin/claude", [])
|
||||
cmd = mock_exec.call_args[0][1]
|
||||
assert "--name" in cmd
|
||||
idx = cmd.index("--name")
|
||||
assert cmd[idx + 1] == "hooks-abc12345"
|
||||
|
||||
Reference in New Issue
Block a user