From 51897dc3e6bac77b2b0c30a45595484410cd1454 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 10 May 2026 11:04:56 +0000 Subject: [PATCH] fix(orchestrator): stop failed phase-transition backend Co-authored-by: Agentic-Worker --- src/symphony/orchestrator.py | 40 +++++++++++++-------- tests/test_orchestrator_phase_transition.py | 24 +++++++++++++ 2 files changed, 50 insertions(+), 14 deletions(-) diff --git a/src/symphony/orchestrator.py b/src/symphony/orchestrator.py index b431182..11e0bf1 100644 --- a/src/symphony/orchestrator.py +++ b/src/symphony/orchestrator.py @@ -831,20 +831,32 @@ async def _rebuild_backend_for_phase( client_tools=tools, ) ) - await new_client.start() - await new_client.initialize() - first_prompt, _ = build_first_turn_prompt( - prompt_template=cfg.prompt_template, - issue=issue, - attempt=attempt, - language=doc_language, - max_turns=cfg.agent.max_turns, - is_rewind=is_rewind, - ) - await new_client.start_session( - initial_prompt=first_prompt, - issue_title=f"{issue.identifier}: {issue.title}", - ) + try: + await new_client.start() + await new_client.initialize() + first_prompt, _ = build_first_turn_prompt( + prompt_template=cfg.prompt_template, + issue=issue, + attempt=attempt, + language=doc_language, + max_turns=cfg.agent.max_turns, + is_rewind=is_rewind, + ) + await new_client.start_session( + initial_prompt=first_prompt, + issue_title=f"{issue.identifier}: {issue.title}", + ) + except BaseException: + try: + await new_client.stop() + except Exception as stop_exc: + log.warning( + "phase_transition_new_stop_failed", + issue_id=issue.id, + identifier=issue.identifier, + error=str(stop_exc), + ) + raise return new_client, first_prompt async def _refresh_issue_state( diff --git a/tests/test_orchestrator_phase_transition.py b/tests/test_orchestrator_phase_transition.py index 886ef48..2b94fd6 100644 --- a/tests/test_orchestrator_phase_transition.py +++ b/tests/test_orchestrator_phase_transition.py @@ -316,6 +316,30 @@ def test_phase_transition_rebuilds_backend_with_fresh_first_prompt( assert second_run[0][1]["prompt"] == first_prompts[1] +def test_phase_transition_stops_new_backend_when_rebuild_initialize_fails( + tmp_path: Path, monkeypatch: pytest.MonkeyPatch +) -> None: + cfg = _make_config(max_turns=5) + issue = _make_issue(state="Todo") + o = _orch(tmp_path) + _seed_running_entry(o, issue, tmp_path) + instances = _install_fake_backend(monkeypatch) + _install_state_sequence(monkeypatch, ["In Progress", "Done"]) + + async def _initialize(self_inst: _FakeBackend) -> None: + self_inst.calls.append(("initialize", {})) + if self_inst.init_id == 1: + raise RuntimeError("second backend init failed") + + monkeypatch.setattr(_FakeBackend, "initialize", _initialize) + + asyncio.run(o._run_agent_attempt(issue, attempt=None, cfg=cfg)) + + assert len(instances) == 2 + second_calls = [name for name, _ in instances[1].calls] + assert second_calls == ["start", "initialize", "stop"] + + def test_same_phase_does_not_restart_backend( tmp_path: Path, monkeypatch: pytest.MonkeyPatch ) -> None: