From d187282179acc717545cbf19e71d81a451dfeb96 Mon Sep 17 00:00:00 2001 From: Clayde Date: Wed, 25 Mar 2026 06:42:01 +0000 Subject: [PATCH] Fix #54: Don't set INTERRUPTED status on run_update failure When plan update fails (usage limit, timeout, or parse error), the issue should stay in AWAITING_*_APPROVAL so the next tick naturally retries run_update with the same new comments. Previously, it set status to INTERRUPTED which caused the orchestrator to retry with run_preliminary (a full re-plan), dropping any comments seen since the plan was posted. Co-Authored-By: Claude Sonnet 4.6 --- src/clayde/tasks/plan.py | 8 -------- tests/test_tasks_plan.py | 4 ++-- 2 files changed, 2 insertions(+), 10 deletions(-) diff --git a/src/clayde/tasks/plan.py b/src/clayde/tasks/plan.py index ab08c04..846636e 100644 --- a/src/clayde/tasks/plan.py +++ b/src/clayde/tasks/plan.py @@ -285,10 +285,6 @@ def run_update(issue_url: str, phase: str) -> None: log.warning("%s during plan update #%d", label, number) accumulate_cost(issue_url, e.cost_eur) span.set_attribute("plan.update_status", "timeout" if isinstance(e, InvocationTimeoutError) else "limit") - update_issue_state(issue_url, { - "status": IssueStatus.INTERRUPTED, - "interrupted_phase": IssueStatus.PRELIMINARY_PLANNING if phase == "preliminary" else IssueStatus.PLANNING, - }) return total_cost = pop_accumulated_cost(issue_url) + result.cost_eur @@ -301,10 +297,6 @@ def run_update(issue_url: str, phase: str) -> None: except ValueError as e: log.error("[%s: %s] Failed to parse update plan response: %s", issue_ref(owner, repo, number), issue.title, e) span.set_attribute("plan.update_status", "parse_error") - update_issue_state(issue_url, { - "status": IssueStatus.INTERRUPTED, - "interrupted_phase": IssueStatus.PRELIMINARY_PLANNING if phase == "preliminary" else IssueStatus.PLANNING, - }) return if updated_plan: diff --git a/tests/test_tasks_plan.py b/tests/test_tasks_plan.py index 16db29b..4ec9bfb 100644 --- a/tests/test_tasks_plan.py +++ b/tests/test_tasks_plan.py @@ -471,8 +471,8 @@ def test_usage_limit_accumulates_cost(self): run_update("url", "preliminary") mock_accum.assert_called_once_with("url", 0.30) - last_call = mock_update.call_args_list[-1] - assert last_call[0][1]["status"] == "interrupted" + # Status must NOT be changed — issue stays in awaiting_*_approval for retry + mock_update.assert_not_called() def test_skips_when_no_new_comments(self): old_comment = MagicMock()