Skip to content

Commit 84dc05d

Browse files
authored
Merge pull request #53 from ryanmac/fix-recursive-workflow-emergency
Emergency fix: Break recursive workflow loop and prevent duplicate is…
2 parents 757476e + 4c07170 commit 84dc05d

4 files changed

Lines changed: 225 additions & 146 deletions

File tree

.conductor/scripts/health-check.py

Lines changed: 65 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -241,8 +241,22 @@ def update_status_issue(self, report, summary_type="daily"):
241241
try:
242242
issues = json.loads(output)
243243
if issues and not should_create_new:
244-
# Use the most recent daily status issue
244+
# Sort by creation date and use the most recent
245+
issues.sort(key=lambda x: x["createdAt"], reverse=True)
245246
status_issue_number = issues[0]["number"]
247+
248+
# Close any duplicate status issues
249+
if len(issues) > 1:
250+
for issue in issues[1:]:
251+
self.run_gh_command(
252+
[
253+
"issue",
254+
"close",
255+
str(issue["number"]),
256+
"-c",
257+
"Closing duplicate status issue",
258+
]
259+
)
246260
elif should_create_new:
247261
# Close old status issues
248262
for issue in issues:
@@ -324,6 +338,42 @@ def update_status_issue(self, report, summary_type="daily"):
324338
else:
325339
# Create new status issue
326340
title = f"{issue_title_prefix} {datetime.utcnow().strftime('%Y-%m-%d')}"
341+
342+
# Double-check no issue was created in the meantime
343+
final_check = self.run_gh_command(
344+
[
345+
"issue",
346+
"list",
347+
"-l",
348+
"conductor:status",
349+
"--state",
350+
"open",
351+
"--limit",
352+
"1",
353+
"--json",
354+
"number",
355+
]
356+
)
357+
358+
if final_check:
359+
try:
360+
existing = json.loads(final_check)
361+
if existing:
362+
# Use existing instead of creating new
363+
self.run_gh_command(
364+
[
365+
"issue",
366+
"edit",
367+
str(existing[0]["number"]),
368+
"--body",
369+
status_content,
370+
]
371+
)
372+
return
373+
except json.JSONDecodeError:
374+
pass
375+
376+
# Create the issue
327377
self.run_gh_command(
328378
[
329379
"issue",
@@ -444,6 +494,20 @@ def main():
444494
args = parser.parse_args()
445495

446496
checker = HealthChecker()
497+
498+
if args.json:
499+
# For JSON output, just return the stale agent count
500+
all_issues = checker.get_conductor_issues()
501+
assigned_tasks = [i for i in all_issues if i.get("assignees")]
502+
checker.check_agent_heartbeats(assigned_tasks)
503+
result = {
504+
"stale_agents": len(checker.stale_agents),
505+
"active_agents": len(checker.active_agents),
506+
"timestamp": datetime.utcnow().isoformat(),
507+
}
508+
print(json.dumps(result))
509+
sys.exit(0)
510+
447511
success = checker.run_checks(args.summary_type)
448512
sys.exit(0 if success else 1)
449513

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#!/bin/bash
2+
# Test script to validate workflow fixes
3+
4+
echo "🧪 Testing workflow fixes..."
5+
6+
# Test 1: Check if workflow file is valid YAML
7+
echo "✓ Checking workflow YAML syntax..."
8+
python -c "import yaml; yaml.safe_load(open('.github/workflows/conductor.yml'))" || exit 1
9+
10+
# Test 2: Verify concurrency group is set
11+
echo "✓ Checking concurrency controls..."
12+
grep -q "concurrency:" .github/workflows/conductor.yml || exit 1
13+
14+
# Test 3: Verify bot exclusion
15+
echo "✓ Checking bot exclusion..."
16+
grep -q "github.actor != 'github-actions\[bot\]'" .github/workflows/conductor.yml || exit 1
17+
18+
# Test 4: Verify removed issue triggers
19+
echo "✓ Checking removed issue triggers..."
20+
! grep -q "^ issues:" .github/workflows/conductor.yml || echo "⚠️ Warning: issues trigger still present"
21+
22+
# Test 5: Check Python scripts can run
23+
echo "✓ Testing Python scripts..."
24+
python .conductor/scripts/health-check.py --json > /dev/null 2>&1 || echo "⚠️ Health check needs GitHub auth"
25+
python .conductor/scripts/update-status.py --json > /dev/null 2>&1 || echo "⚠️ Update status needs GitHub auth"
26+
27+
echo "✅ Basic tests complete!"
28+
echo ""
29+
echo "📋 Summary of fixes:"
30+
echo "- Removed issue/comment triggers to prevent recursion"
31+
echo "- Added concurrency controls to prevent multiple runs"
32+
echo "- Added bot exclusion to prevent self-triggering"
33+
echo "- Improved duplicate issue detection"
34+
echo "- Fixed authentication to use CONDUCTOR_GITHUB_TOKEN"
35+
echo "- Added retry logic and better error handling"

.conductor/scripts/update-status.py

Lines changed: 62 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@ def run_gh_command(args):
1717
)
1818
return result.stdout.strip()
1919
except subprocess.CalledProcessError as e:
20+
if "--json" in args:
21+
# For JSON commands, return empty JSON array/object
22+
return "[]" if "list" in args else "{}"
2023
print(f"❌ GitHub CLI error: {e.stderr}")
2124
return ""
2225
except FileNotFoundError:
@@ -26,50 +29,66 @@ def run_gh_command(args):
2629

2730
def get_status_issue():
2831
"""Get or create the status issue"""
29-
# Check if status issue exists
30-
output = run_gh_command(
31-
[
32-
"issue",
33-
"list",
34-
"-l",
35-
"conductor:status",
36-
"--state",
37-
"open",
38-
"--limit",
39-
"1",
40-
"--json",
41-
"number,title",
42-
]
43-
)
44-
45-
if output:
46-
try:
47-
issues = json.loads(output)
48-
if issues:
49-
return issues[0]["number"]
50-
except json.JSONDecodeError:
51-
pass
52-
53-
# Create new status issue if it doesn't exist
54-
print("📝 Creating new status issue...")
55-
output = run_gh_command(
56-
[
57-
"issue",
58-
"create",
59-
"--title",
60-
"🏥 Code-Conductor System Status",
61-
"--body",
62-
"This issue tracks the system status and health metrics "
63-
"for Code-Conductor.",
64-
"--label",
65-
"conductor:status",
66-
]
67-
)
32+
max_retries = 3
33+
for attempt in range(max_retries):
34+
# Check if status issue exists
35+
output = run_gh_command(
36+
[
37+
"issue",
38+
"list",
39+
"-l",
40+
"conductor:status",
41+
"--state",
42+
"open",
43+
"--limit",
44+
"10", # Get more to handle duplicates
45+
"--json",
46+
"number,title,createdAt",
47+
]
48+
)
6849

69-
# Extract issue number from output
70-
if output and "#" in output:
71-
issue_number = output.split("#")[1].split()[0]
72-
return int(issue_number)
50+
if output:
51+
try:
52+
issues = json.loads(output)
53+
if issues:
54+
# Return the most recent one
55+
issues.sort(key=lambda x: x["createdAt"], reverse=True)
56+
return issues[0]["number"]
57+
except json.JSONDecodeError:
58+
if attempt < max_retries - 1:
59+
print("⚠️ Failed to parse issues, retrying...")
60+
continue
61+
62+
# Only create if we're sure there isn't one
63+
if attempt == max_retries - 1:
64+
print("📝 Creating new status issue...")
65+
try:
66+
output = run_gh_command(
67+
[
68+
"issue",
69+
"create",
70+
"--title",
71+
"🏥 Code-Conductor System Status",
72+
"--body",
73+
"This issue tracks the system status and health metrics "
74+
"for Code-Conductor.",
75+
"--label",
76+
"conductor:status",
77+
]
78+
)
79+
80+
# Extract issue number from output
81+
if output and "#" in output:
82+
issue_number = output.split("#")[1].split()[0]
83+
return int(issue_number)
84+
except Exception as e:
85+
print(f"❌ Failed to create issue: {e}")
86+
87+
# Wait before retry
88+
if attempt < max_retries - 1:
89+
import time
90+
91+
time.sleep(2)
7392

7493
return None
7594

0 commit comments

Comments
 (0)