Skip to content

Commit 469b7ca

Browse files
committed
fix(run): reject empty tasks
1 parent 94ab1a9 commit 469b7ca

2 files changed

Lines changed: 52 additions & 1 deletion

File tree

src/brigade/cli/run.py

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,12 @@ def _terminalize_escaped_run(
8080
raise
8181

8282

83+
def _non_empty_task(value: str) -> str:
84+
if not value.strip():
85+
raise argparse.ArgumentTypeError("must not be empty or whitespace")
86+
return value
87+
88+
8389
def _non_negative_seconds(value: str) -> float:
8490
try:
8591
parsed = float(value)
@@ -93,7 +99,11 @@ def _non_negative_seconds(value: str) -> float:
9399
def register(sub: argparse._SubParsersAction) -> None:
94100
# run
95101
p_run = sub.add_parser("run", help="Run a bounded cross-model orchestration task.")
96-
p_run.add_argument("task", help="Task for the aboyeur to plan, dispatch, and synthesize.")
102+
p_run.add_argument(
103+
"task",
104+
type=_non_empty_task,
105+
help="Task for the aboyeur to plan, dispatch, and synthesize.",
106+
)
97107
p_run.add_argument(
98108
"--roster",
99109
type=Path,

tests/test_run_cli.py

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from brigade import cli
1515
from brigade import localio
1616
from brigade import proc
17+
from brigade import roster
1718
from brigade import runguard
1819
from brigade import runs_cmd
1920

@@ -35,6 +36,46 @@ def test_run_cli_rejects_missing_cwd(tmp_path, capsys):
3536
assert "--cwd is not a directory" in capsys.readouterr().err
3637

3738

39+
@pytest.mark.parametrize("task", ["", " \t\n"])
40+
def test_run_cli_rejects_blank_task_before_run_setup(tmp_path, capsys, monkeypatch, task):
41+
config_dir = tmp_path / ".brigade"
42+
config_dir.mkdir()
43+
(config_dir / "roster.toml").write_text('orchestrator = "chef"\n\n[agents.chef]\ncli = "codex"\nrole = "plan"\n')
44+
runs_dir = config_dir / "runs"
45+
calls = []
46+
resolve_roster = roster.resolve_roster
47+
48+
def tracked_resolve_roster(*args, **kwargs):
49+
calls.append("resolve_roster")
50+
return resolve_roster(*args, **kwargs)
51+
52+
def tracked_make_run_dir(*args, **kwargs):
53+
calls.append("make_run_dir")
54+
return runs_dir / "unexpected"
55+
56+
@contextmanager
57+
def tracked_run_lock(*args, **kwargs):
58+
calls.append("run_lock")
59+
yield
60+
61+
def tracked_run(*args, **kwargs):
62+
calls.append("run")
63+
return 0
64+
65+
monkeypatch.setattr(roster, "resolve_roster", tracked_resolve_roster)
66+
monkeypatch.setattr(aboyeur, "make_run_dir", tracked_make_run_dir)
67+
monkeypatch.setattr(runguard, "run_lock", tracked_run_lock)
68+
monkeypatch.setattr(aboyeur, "run", tracked_run)
69+
70+
with pytest.raises(SystemExit) as exc:
71+
cli.main(["run", task, "--cwd", str(tmp_path)])
72+
73+
assert exc.value.code == 2
74+
assert "argument task: must not be empty or whitespace" in capsys.readouterr().err
75+
assert calls == []
76+
assert not runs_dir.exists()
77+
78+
3879
def test_run_cli_loads_roster_and_dispatches(tmp_path, monkeypatch):
3980
roster_path = tmp_path / "roster.toml"
4081
roster_path.write_text(

0 commit comments

Comments
 (0)