From 6c9064bca2bc5c0c7f5528d8fcf26474f6565684 Mon Sep 17 00:00:00 2001 From: Dan Childers Date: Thu, 23 Jul 2026 15:13:21 -0400 Subject: [PATCH 1/2] fix: increment retry_count when prepare_workspace fails MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The prepare_workspace exception handler set last_error but never incremented retry_count, so the routing function's retry_count >= max_retries check never triggered — causing an unbounded retry loop. Co-Authored-By: Claude Opus 4.6 --- src/forge/workflow/nodes/implementation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/src/forge/workflow/nodes/implementation.py b/src/forge/workflow/nodes/implementation.py index 5514cc26..e15e8353 100644 --- a/src/forge/workflow/nodes/implementation.py +++ b/src/forge/workflow/nodes/implementation.py @@ -66,6 +66,7 @@ async def implement_task(state: WorkflowState) -> WorkflowState: **state, "last_error": str(exc), "current_node": implementation_node, + "retry_count": state.get("retry_count", 0) + 1, } same_workspace_survived = local_workspace_survived and workspace_path == recorded_workspace From ec5d3a3784da4ad66f337a7e7878e783e58c1042 Mon Sep 17 00:00:00 2001 From: Dan Childers Date: Thu, 23 Jul 2026 15:18:37 -0400 Subject: [PATCH 2/2] test: verify prepare_workspace failure increments retry_count Adds a test that starts with retry_count=1 and asserts it becomes 2 after a prepare_workspace failure, ensuring the routing function can break the loop after max_retries. Co-Authored-By: Claude Opus 4.6 --- tests/unit/workflow/nodes/test_implementation.py | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/tests/unit/workflow/nodes/test_implementation.py b/tests/unit/workflow/nodes/test_implementation.py index 90de8ba8..50fa7bff 100644 --- a/tests/unit/workflow/nodes/test_implementation.py +++ b/tests/unit/workflow/nodes/test_implementation.py @@ -17,13 +17,14 @@ def _make_state( current_repo="acme/backend", tasks_by_repo=None, implemented_tasks=None, + retry_count=0, ): return { "ticket_key": ticket_key, "ticket_type": ticket_type, "current_node": "implement_task", "is_paused": False, - "retry_count": 0, + "retry_count": retry_count, "last_error": None, "workspace_path": workspace_path, "current_task_key": current_task_key, @@ -211,6 +212,17 @@ async def test_bug_missing_workspace_keeps_bug_implementation_node(self): assert result["current_node"] == "implement_bug_fix" assert result["last_error"] == "Workspace not set up" + @pytest.mark.asyncio + async def test_prepare_workspace_failure_increments_retry_count(self): + """Workspace setup failures must increment retry_count to avoid unbounded loops.""" + from forge.workflow.nodes.implementation import implement_task + + state = _make_state(workspace_path=None, retry_count=1) + result = await implement_task(state) + + assert result["retry_count"] == 2 + assert result["last_error"] is not None + @pytest.mark.asyncio async def test_feature_container_failure_uses_feature_implementation_node(self): """Feature container failures must not checkpoint bug workflow node names."""