From bb50d0a91d3e985183ffefff433bbb430329ccaf Mon Sep 17 00:00:00 2001 From: Alex Date: Thu, 17 Sep 2026 12:39:41 +0100 Subject: [PATCH] fix: refuse to give the API the mock LLM's port Both preflights passed for `dev --mock-llm --port 8090`: the port was free, and then the mock and the API were each handed it. One child could not bind, and the API was pointed at that port as its model server while trying to listen on it. --- docsgpt/deploy/commands.py | 6 ++++++ tests/deploy/test_doctor.py | 14 ++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/docsgpt/deploy/commands.py b/docsgpt/deploy/commands.py index b8f063aa..94a144f6 100644 --- a/docsgpt/deploy/commands.py +++ b/docsgpt/deploy/commands.py @@ -739,6 +739,12 @@ def dev(args, context: Optional[Context] = None) -> int: f"port {args.port} is already in use, so the API cannot bind it. Stop what is on it " f"(a previous `docsgpt dev`, or `docsgpt down` for an install), or pass --port." ) + if getattr(args, "mock_llm", False) and args.port == dev_module.MOCK_LLM_PORT: + # Both checks below would pass: the port really is free, and then two children want it. + raise DeployError( + f"port {args.port} is where the mock LLM listens, so the API cannot have it too. " + "Give the API another port with --port." + ) if getattr(args, "mock_llm", False) and not _port_is_free(dev_module.MOCK_LLM_PORT): # It starts first and the others are pointed at it, so a busy port here would surface as the # API talking to someone else's server, or as a child exiting once everything else is up. diff --git a/tests/deploy/test_doctor.py b/tests/deploy/test_doctor.py index c0a52b28..4acca1d5 100644 --- a/tests/deploy/test_doctor.py +++ b/tests/deploy/test_doctor.py @@ -66,6 +66,20 @@ class TestDevCommand: _run(["dev", "--mock-llm", "--port", str(self._free_port())], _context()) assert started == [], "nothing is spawned when the mock LLM has nowhere to listen" + def test_the_api_cannot_be_given_the_mock_llm_port(self, monkeypatch, tmp_path): + """Both free-port checks pass here: the port is free, and then two children want it.""" + from docsgpt.deploy import dev as dev_module + + self._checkout(monkeypatch, tmp_path) + (tmp_path / "scripts").mkdir() + (tmp_path / "scripts" / "mock_llm.py").write_text("", encoding="utf-8") + started = [] + monkeypatch.setattr(dev_module, "run", lambda children, **kwargs: started.append(children) or 0) + monkeypatch.setattr(dev_module, "MOCK_LLM_PORT", self._free_port()) + with pytest.raises(DeployError, match="mock LLM listens"): + _run(["dev", "--mock-llm", "--port", str(dev_module.MOCK_LLM_PORT)], _context()) + assert started == [], "nothing is spawned when the two would collide" + def test_it_runs_the_children_it_planned_and_says_where(self, monkeypatch, tmp_path, capsys): from docsgpt.deploy import dev as dev_module