Claude Code Claude Opus 4.6 commited on
Commit
f729048
·
1 Parent(s): 5cbdd00

god: Fix manual terminate+task blocking - allow immediate task reassignment

Browse files

[PROBLEM] When agents sent [ACTION: terminate_cc] followed by [TASK] in the
same message, the task submission condition was checked BEFORE the terminate_cc
action was processed. This caused:
1. has_manual_terminate bypass allowed submission even with cc_status["running"]=True
2. Race condition where terminate_cc was processed after task submission
3. Duplicate action history entries

[FIX] Process [ACTION: terminate_cc] BEFORE [TASK] block (now #1b, before #2).
This ensures cc_status["running"] is False when task submission condition runs,
eliminating the need for has_manual_terminate bypass and preventing race conditions.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

Files changed (1) hide show
  1. scripts/conversation-loop.py +9 -12
scripts/conversation-loop.py CHANGED
@@ -1646,20 +1646,23 @@ def parse_and_execute_turn(raw_text, ctx):
1646
  task_assigned = True
1647
  return raw_text, results, task_assigned
1648
 
 
 
 
 
 
 
 
1649
  # 2. Handle [TASK]...[/TASK] → Claude Code
1650
  task_match = re.search(r'\[TASK\](.*?)\[/TASK\]', raw_text, re.DOTALL)
1651
  if task_match:
1652
  task_desc = task_match.group(1).strip()
1653
- # Check if this message also contains [ACTION: terminate_cc]
1654
- # If so, the termination will be processed before task submission (below),
1655
- # so we should allow this task to proceed even if cc_status["running"] is currently True.
1656
- has_manual_terminate = re.search(r'\[ACTION:\s*terminate_cc\]', raw_text)
1657
  # task_assigned is set to True ONLY when task is actually submitted, not when blocked
1658
  if not task_desc:
1659
  results.append({"action": "task", "result": "Empty task description."})
1660
  elif child_state["stage"] in ("BUILDING", "RESTARTING", "APP_STARTING"):
1661
  results.append({"action": "task", "result": f"BLOCKED: Cain is {child_state['stage']}. Wait for it to finish."})
1662
- elif cc_status["running"] and not has_manual_terminate:
1663
  # LOW-PUSH-FREQUENCY EMERGENCY: If push frequency is critically low and task has been running 60s+, allow task handoff
1664
  # This prevents all-talk-no-action when agents get stuck after 1 push
1665
  global _push_count, _turns_since_last_push, _push_count_this_task
@@ -1684,8 +1687,7 @@ def parse_and_execute_turn(raw_text, ctx):
1684
 
1685
  # Task submission block - handles both normal flow and post-zero-push-termination flow
1686
  # Only proceeds if not blocked above (results is empty or only contains termination notice)
1687
- # Also allows submission if this message contains [ACTION: terminate_cc] (manual termination)
1688
- if (not results or any("terminate_cc" in r.get("action", "") for r in results)) and (cc_status["running"] == False or has_manual_terminate):
1689
  # Check cooldown
1690
  check_and_clear_cooldown()
1691
  if last_rebuild_trigger_at > 0:
@@ -1737,11 +1739,6 @@ def parse_and_execute_turn(raw_text, ctx):
1737
  result = action_send_bubble(bubble_match.group(1).strip())
1738
  results.append({"action": "send_bubble", "result": result})
1739
 
1740
- # 5. Handle [ACTION: terminate_cc] (terminate stuck Claude Code)
1741
- if re.search(r'\[ACTION:\s*terminate_cc\]', raw_text):
1742
- result = action_terminate_cc()
1743
- results.append({"action": "terminate_cc", "result": result})
1744
-
1745
  # Activate deferred cooldown
1746
  if _pending_cooldown:
1747
  last_rebuild_trigger_at = time.time()
 
1646
  task_assigned = True
1647
  return raw_text, results, task_assigned
1648
 
1649
+ # 1b. Handle [ACTION: terminate_cc] FIRST (before task submission)
1650
+ # This ensures cc_status["running"] is False before task submission check,
1651
+ # preventing race conditions when agents terminate+submit in same message.
1652
+ if re.search(r'\[ACTION:\s*terminate_cc\]', raw_text):
1653
+ result = action_terminate_cc()
1654
+ results.append({"action": "terminate_cc", "result": result})
1655
+
1656
  # 2. Handle [TASK]...[/TASK] → Claude Code
1657
  task_match = re.search(r'\[TASK\](.*?)\[/TASK\]', raw_text, re.DOTALL)
1658
  if task_match:
1659
  task_desc = task_match.group(1).strip()
 
 
 
 
1660
  # task_assigned is set to True ONLY when task is actually submitted, not when blocked
1661
  if not task_desc:
1662
  results.append({"action": "task", "result": "Empty task description."})
1663
  elif child_state["stage"] in ("BUILDING", "RESTARTING", "APP_STARTING"):
1664
  results.append({"action": "task", "result": f"BLOCKED: Cain is {child_state['stage']}. Wait for it to finish."})
1665
+ elif cc_status["running"]:
1666
  # LOW-PUSH-FREQUENCY EMERGENCY: If push frequency is critically low and task has been running 60s+, allow task handoff
1667
  # This prevents all-talk-no-action when agents get stuck after 1 push
1668
  global _push_count, _turns_since_last_push, _push_count_this_task
 
1687
 
1688
  # Task submission block - handles both normal flow and post-zero-push-termination flow
1689
  # Only proceeds if not blocked above (results is empty or only contains termination notice)
1690
+ if (not results or any("terminate_cc" in r.get("action", "") for r in results)) and cc_status["running"] == False:
 
1691
  # Check cooldown
1692
  check_and_clear_cooldown()
1693
  if last_rebuild_trigger_at > 0:
 
1739
  result = action_send_bubble(bubble_match.group(1).strip())
1740
  results.append({"action": "send_bubble", "result": result})
1741
 
 
 
 
 
 
1742
  # Activate deferred cooldown
1743
  if _pending_cooldown:
1744
  last_rebuild_trigger_at = time.time()