fix: run agent in TUI mode so tmux attach shows live progress (#118)

Previously claude was launched with -p (print mode) which produces no
visible TUI.  Attaching to the session with `tmux attach -t issue-NNN`
showed a blank terminal.  Removing -p makes Claude run its interactive
TUI inside the tmux pane, so the session is fully watchable.

Add scripts/test_agent_loop.py covering _start_agent command
construction and state file round-trips.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
Thomas SharedInbox
2026-05-16 23:26:58 +02:00
co-authored by Claude Sonnet 4.6
parent 606958e675
commit 81fd03102b
2 changed files with 134 additions and 3 deletions
+119
View File
@@ -0,0 +1,119 @@
#!/usr/bin/env python3
"""Tests for scripts/agent_loop.py."""
import json
import unittest
from pathlib import Path
from unittest.mock import MagicMock, call, patch
class TestStartAgent(unittest.TestCase):
"""Verify that _start_agent builds the correct tmux / claude command."""
def _run(self, prompt: str, session_name: str):
"""Call _start_agent with all subprocess.run calls mocked out."""
calls: list = []
def fake_run(cmd, **kwargs):
calls.append(cmd)
return MagicMock(returncode=0)
with (
patch("scripts.agent_loop.subprocess.run", side_effect=fake_run),
patch("scripts.agent_loop.Path.mkdir"),
):
import scripts.agent_loop as al
al._start_agent(prompt, session_name)
return calls
def _get_shell_cmd(self, calls: list) -> str:
"""Extract the shell command string from tmux new-session call."""
for cmd in calls:
if "new-session" in cmd:
# last element is the shell_cmd passed to bash -c
return cmd[-1]
self.fail("tmux new-session call not found")
def test_no_print_flag(self):
calls = self._run("Do something", "issue-1")
shell_cmd = self._get_shell_cmd(calls)
self.assertNotIn(" -p ", shell_cmd)
self.assertNotIn("--print", shell_cmd)
def test_prompt_included_in_cmd(self):
calls = self._run("Fix the bug", "issue-42")
shell_cmd = self._get_shell_cmd(calls)
self.assertIn("Fix the bug", shell_cmd)
def test_session_name_in_cmd(self):
calls = self._run("task prompt", "issue-99")
shell_cmd = self._get_shell_cmd(calls)
self.assertIn("issue-99", shell_cmd)
def test_dangerously_skip_permissions_present(self):
calls = self._run("prompt", "ci-fix")
shell_cmd = self._get_shell_cmd(calls)
self.assertIn("--dangerously-skip-permissions", shell_cmd)
def test_prompt_with_special_chars_is_quoted(self):
prompt = "Fix issue #42; rm -rf /"
calls = self._run(prompt, "issue-42")
shell_cmd = self._get_shell_cmd(calls)
# The dangerous part should be inside quotes, not interpreted by shell
self.assertNotIn("; rm -rf /", shell_cmd.split("'")[-1])
def test_tmux_new_session_is_detached(self):
calls = self._run("prompt", "issue-1")
new_session_call = next(c for c in calls if "new-session" in c)
self.assertIn("-d", new_session_call)
def test_tmux_new_session_uses_correct_name(self):
calls = self._run("prompt", "issue-7")
new_session_call = next(c for c in calls if "new-session" in c)
self.assertIn("issue-7", new_session_call)
def test_pipe_pane_called_for_logging(self):
calls = self._run("prompt", "issue-1")
pipe_pane_calls = [c for c in calls if "pipe-pane" in c]
self.assertEqual(len(pipe_pane_calls), 1)
def test_returns_session_name(self):
with (
patch("scripts.agent_loop.subprocess.run", return_value=MagicMock(returncode=0)),
patch("scripts.agent_loop.Path.mkdir"),
):
import scripts.agent_loop as al
result = al._start_agent("prompt", "issue-55")
self.assertEqual(result, "issue-55")
class TestWriteAndReadState(unittest.TestCase):
def test_roundtrip(self):
import tempfile
import scripts.agent_loop as al
with tempfile.NamedTemporaryFile(suffix=".json", delete=False) as f:
tmp = Path(f.name)
original = al.STATE_FILE
try:
al.STATE_FILE = tmp
al._write_state("issue-10", 10, "issue")
state = al._read_state()
self.assertEqual(state["tmux_session"], "issue-10")
self.assertEqual(state["issue"], 10)
self.assertEqual(state["type"], "issue")
finally:
al.STATE_FILE = original
tmp.unlink(missing_ok=True)
def test_read_missing_returns_none(self):
import scripts.agent_loop as al
original = al.STATE_FILE
try:
al.STATE_FILE = Path("/nonexistent/state.json")
self.assertIsNone(al._read_state())
finally:
al.STATE_FILE = original
if __name__ == "__main__":
unittest.main()