-
-
Notifications
You must be signed in to change notification settings - Fork 1.7k
fix: resolve QA validation deadlock when subtasks are stuck or failed #1853
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: develop
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -28,7 +28,7 @@ | |
| get_phase_model_betas, | ||
| ) | ||
| from phase_event import ExecutionPhase, emit_phase | ||
| from progress import count_subtasks, is_build_complete | ||
| from progress import count_subtasks, is_build_ready_for_qa | ||
| from security.constants import PROJECT_DIR_ENV_VAR | ||
| from task_logger import ( | ||
| LogPhase, | ||
|
|
@@ -114,14 +114,25 @@ async def run_qa_validation_loop( | |
| # Initialize task logger for the validation phase | ||
| task_logger = get_task_logger(spec_dir) | ||
|
|
||
| # Verify build is complete | ||
| if not is_build_complete(spec_dir): | ||
| debug_warning("qa_loop", "Build is not complete, cannot run QA") | ||
| print("\n❌ Build is not complete. Cannot run QA validation.") | ||
| completed, total = count_subtasks(spec_dir) | ||
| debug("qa_loop", "Build progress", completed=completed, total=total) | ||
| print(f" Progress: {completed}/{total} subtasks completed") | ||
| return False | ||
| # Check if there's pending human feedback that needs to be processed | ||
| fix_request_file = spec_dir / "QA_FIX_REQUEST.md" | ||
| has_human_feedback = fix_request_file.exists() | ||
|
|
||
| # Human feedback takes priority — if the user explicitly asked to proceed, | ||
| # skip the build completeness gate entirely | ||
| if not has_human_feedback: | ||
| # Verify build is ready for QA (all subtasks in terminal state) | ||
| if not is_build_ready_for_qa(spec_dir): | ||
| debug_warning( | ||
| "qa_loop", "Build is not ready for QA - subtasks still in progress" | ||
| ) | ||
| print("\n❌ Build is not ready for QA validation.") | ||
| completed, total = count_subtasks(spec_dir) | ||
| debug("qa_loop", "Build progress", completed=completed, total=total) | ||
| print( | ||
| f" Progress: {completed}/{total} subtasks in terminal state (completed/failed/stuck)" | ||
| ) | ||
| return False | ||
|
Comment on lines
117
to
135
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧩 Analysis chain🏁 Script executed: #!/bin/bash
# Find the count_subtasks function definition
rg -n 'def count_subtasks' --type=py -A 15Repository: AndyMik90/Auto-Claude Length of output: 1873 🏁 Script executed: #!/bin/bash
# Also search for is_build_ready_for_qa to understand the terminal state logic
rg -n 'def is_build_ready_for_qa' --type=py -A 20Repository: AndyMik90/Auto-Claude Length of output: 1568 🏁 Script executed: #!/bin/bash
# Check what files are in the qa directory
find apps/backend/qa -type f -name "*.py" | head -20Repository: AndyMik90/Auto-Claude Length of output: 250 🏁 Script executed: #!/bin/bash
# Get full count_subtasks implementation
sed -n '33,65p' apps/backend/core/progress.pyRepository: AndyMik90/Auto-Claude Length of output: 911 🏁 Script executed: #!/bin/bash
# Get full is_build_ready_for_qa implementation
sed -n '118,170p' apps/backend/core/progress.pyRepository: AndyMik90/Auto-Claude Length of output: 1926 🏁 Script executed: #!/bin/bash
# Check count_subtasks_detailed full implementation
sed -n '66,117p' apps/backend/core/progress.pyRepository: AndyMik90/Auto-Claude Length of output: 1370 🏁 Script executed: #!/bin/bash
# Get the context around line 130 in loop.py
sed -n '115,135p' apps/backend/qa/loop.pyRepository: AndyMik90/Auto-Claude Length of output: 1087 🏁 Script executed: #!/bin/bash
# Check if count_subtasks_detailed is used anywhere in loop.py or related files
rg -n 'count_subtasks_detailed' --type=py apps/backend/qa/Repository: AndyMik90/Auto-Claude Length of output: 47 Update the progress message to reflect terminal state semantics. When the QA readiness gate fails (line 130), the message shows Either update the message to show terminal/total count (e.g., 🤖 Prompt for AI Agents |
||
|
|
||
| # Emit phase event at start of QA validation (before any early returns) | ||
| emit_phase(ExecutionPhase.QA_REVIEW, "Starting QA validation") | ||
|
|
@@ -136,10 +147,6 @@ async def run_qa_validation_loop( | |
| f"[Fast Mode] {'ENABLED' if fast_mode else 'disabled'} for QA validation", | ||
| ) | ||
|
|
||
| # Check if there's pending human feedback that needs to be processed | ||
| fix_request_file = spec_dir / "QA_FIX_REQUEST.md" | ||
| has_human_feedback = fix_request_file.exists() | ||
|
|
||
| # Check if already approved - but if there's human feedback, we need to process it first | ||
| if is_qa_approved(spec_dir) and not has_human_feedback: | ||
| debug_success("qa_loop", "Build already approved by QA") | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -21,6 +21,8 @@ | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from enum import Enum | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from pathlib import Path | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| from core.file_utils import write_json_atomic | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Recovery manager configuration | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ATTEMPT_WINDOW_SECONDS = 7200 # Only count attempts within last 2 hours | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| MAX_ATTEMPT_HISTORY_PER_SUBTASK = 50 # Cap stored attempts per subtask | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -514,6 +516,36 @@ def mark_subtask_stuck(self, subtask_id: str, reason: str) -> None: | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| self._save_attempt_history(history) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # Also update the subtask status in implementation_plan.json | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| # so that other callers (like is_build_ready_for_qa) see accurate status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| plan_file = self.spec_dir / "implementation_plan.json" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if plan_file.exists(): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| with open(plan_file, encoding="utf-8") as f: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| plan = json.load(f) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updated = False | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for phase in plan.get("phases", []): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| for subtask in phase.get("subtasks", []): | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if subtask.get("id") == subtask_id: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| subtask["status"] = "failed" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| stuck_note = f"Marked as stuck: {reason}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| existing = subtask.get("actual_output", "") | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| subtask["actual_output"] = ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"{stuck_note}\n{existing}" if existing else stuck_note | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| updated = True | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if updated: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| break | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if updated: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| write_json_atomic(plan_file, plan, indent=2) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| except (OSError, json.JSONDecodeError, UnicodeDecodeError) as e: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| logger.warning( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| f"Failed to update implementation_plan.json for stuck subtask {subtask_id}: {e}" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+519
to
+547
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🧹 Nitpick | 🔵 Trivial Variable Line 507 defines Proposed rename- existing = subtask.get("actual_output", "")
+ prior_output = subtask.get("actual_output", "")
subtask["actual_output"] = (
- f"{stuck_note}\n{existing}" if existing else stuck_note
+ f"{stuck_note}\n{prior_output}" if prior_output else stuck_note
)📝 Committable suggestion
Suggested change
🤖 Prompt for AI Agents |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| def get_stuck_subtasks(self) -> list[dict]: | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| """ | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Get all subtasks marked as stuck. | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🧹 Nitpick | 🔵 Trivial
Import of
count_subtasksis retained but only used in the misleading progress message.count_subtasksis still imported on line 31 alongsideis_build_ready_for_qa. Once the progress message is fixed (see above), this import may become unused. Keep this in mind when addressing the progress message.🤖 Prompt for AI Agents