claude-evolve 1.8.4 → 1.8.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/bin/claude-evolve-run +26 -50
- package/bin/claude-evolve-worker +12 -3
- package/lib/evolution_csv.py +66 -0
- package/package.json +1 -1
package/bin/claude-evolve-run
CHANGED
|
@@ -378,6 +378,12 @@ try:
|
|
|
378
378
|
if len(rows[i]) > 4 and rows[i][4] == 'running':
|
|
379
379
|
rows[i][4] = ''
|
|
380
380
|
changed_count += 1
|
|
381
|
+
|
|
382
|
+
# Reset failed-parent-missing to pending at startup - give them another chance
|
|
383
|
+
for i in range(1, len(rows)):
|
|
384
|
+
if len(rows[i]) > 4 and rows[i][4] == 'failed-parent-missing':
|
|
385
|
+
rows[i][4] = 'pending'
|
|
386
|
+
changed_count += 1
|
|
381
387
|
|
|
382
388
|
# Check for missing Python files for completed/failed candidates
|
|
383
389
|
for i in range(1, len(rows)):
|
|
@@ -577,43 +583,17 @@ while true; do
|
|
|
577
583
|
if [[ -f "$FULL_CSV_PATH" && ${#worker_pids[@]} -eq 0 ]]; then
|
|
578
584
|
echo "[DISPATCHER] Checking for stuck candidates (no active workers)..."
|
|
579
585
|
"$PYTHON_CMD" -c "
|
|
580
|
-
import csv
|
|
581
586
|
import sys
|
|
582
|
-
|
|
587
|
+
sys.path.insert(0, '$SCRIPT_DIR/..')
|
|
588
|
+
from lib.evolution_csv import EvolutionCSV
|
|
583
589
|
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
has_header = rows and rows[0] and rows[0][0].lower() == 'id'
|
|
592
|
-
start_idx = 1 if has_header else 0
|
|
593
|
-
|
|
594
|
-
for i in range(start_idx, len(rows)):
|
|
595
|
-
if len(rows[i]) > 4:
|
|
596
|
-
status = rows[i][4].strip() if rows[i][4] else ''
|
|
597
|
-
candidate_id = rows[i][0] if rows[i] else ''
|
|
598
|
-
|
|
599
|
-
# Reset 'running' when no workers are active
|
|
600
|
-
if status == 'running':
|
|
601
|
-
print(f'[INFO] Resetting stuck running candidate: {candidate_id}', file=sys.stderr)
|
|
602
|
-
rows[i][4] = 'pending'
|
|
603
|
-
reset_count += 1
|
|
604
|
-
# Reset unknown statuses like 'ready'
|
|
605
|
-
elif status not in ['', 'pending', 'complete', 'failed', 'failed-ai-retry',
|
|
606
|
-
'failed-retry1', 'failed-retry2', 'failed-retry3', 'skipped']:
|
|
607
|
-
print(f'[WARN] Resetting unknown status \"{status}\" to pending: {candidate_id}', file=sys.stderr)
|
|
608
|
-
rows[i][4] = 'pending'
|
|
609
|
-
reset_count += 1
|
|
610
|
-
|
|
611
|
-
if reset_count > 0:
|
|
612
|
-
with open(csv_file + '.tmp', 'w', newline='') as f:
|
|
613
|
-
csv.writer(f).writerows(rows)
|
|
614
|
-
Path(csv_file + '.tmp').rename(csv_file)
|
|
615
|
-
print(f'[INFO] Reset {reset_count} stuck/unknown candidates to pending', file=sys.stderr)
|
|
616
|
-
" || true
|
|
590
|
+
try:
|
|
591
|
+
with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
592
|
+
reset_count = csv.reset_stuck_candidates()
|
|
593
|
+
except Exception as e:
|
|
594
|
+
print(f'[ERROR] Failed to reset stuck candidates: {e}', file=sys.stderr)
|
|
595
|
+
sys.exit(1)
|
|
596
|
+
"
|
|
617
597
|
fi
|
|
618
598
|
fi
|
|
619
599
|
|
|
@@ -635,21 +615,17 @@ if reset_count > 0:
|
|
|
635
615
|
# Before blocking, do final check for stuck work (immediate, not periodic)
|
|
636
616
|
echo "[DISPATCHER] Performing final verification for stuck candidates..."
|
|
637
617
|
stuck_work_count=$("$PYTHON_CMD" -c "
|
|
638
|
-
import
|
|
639
|
-
|
|
640
|
-
|
|
641
|
-
|
|
642
|
-
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
(status and status not in ['', 'pending', 'complete', 'failed', 'skipped',
|
|
650
|
-
'failed-ai-retry', 'failed-retry1', 'failed-retry2', 'failed-retry3']):
|
|
651
|
-
stuck += 1
|
|
652
|
-
print(stuck)
|
|
618
|
+
import sys
|
|
619
|
+
sys.path.insert(0, '$SCRIPT_DIR/..')
|
|
620
|
+
from lib.evolution_csv import EvolutionCSV
|
|
621
|
+
|
|
622
|
+
try:
|
|
623
|
+
with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
624
|
+
stuck = csv.count_stuck_candidates()
|
|
625
|
+
print(stuck)
|
|
626
|
+
except Exception as e:
|
|
627
|
+
print(f'[ERROR] Failed to count stuck candidates: {e}', file=sys.stderr)
|
|
628
|
+
print('0') # Default to 0 on error
|
|
653
629
|
" 2>/dev/null || echo "0")
|
|
654
630
|
|
|
655
631
|
if [[ $stuck_work_count -gt 0 ]]; then
|
package/bin/claude-evolve-worker
CHANGED
|
@@ -297,13 +297,22 @@ with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
|
297
297
|
if [[ "$current_status" == "complete" ]]; then
|
|
298
298
|
echo "[WORKER-$$] Already evaluated - skipping"
|
|
299
299
|
# Reset status back to complete since get_next_pending_candidate() set it to running
|
|
300
|
-
"$PYTHON_CMD" -c "
|
|
300
|
+
if ! "$PYTHON_CMD" -c "
|
|
301
301
|
import sys
|
|
302
302
|
sys.path.insert(0, '$SCRIPT_DIR/..')
|
|
303
303
|
from lib.evolution_csv import EvolutionCSV
|
|
304
304
|
with EvolutionCSV('$FULL_CSV_PATH') as csv:
|
|
305
|
-
csv.update_candidate_status('$candidate_id', 'complete')
|
|
306
|
-
|
|
305
|
+
success = csv.update_candidate_status('$candidate_id', 'complete')
|
|
306
|
+
if not success:
|
|
307
|
+
print(f'ERROR: Failed to update status for $candidate_id', file=sys.stderr)
|
|
308
|
+
sys.exit(1)
|
|
309
|
+
"; then
|
|
310
|
+
echo "[WORKER-$$] ERROR: Failed to reset status to complete for $candidate_id" >&2
|
|
311
|
+
# Don't clear CURRENT_CANDIDATE_ID so cleanup handler can try again
|
|
312
|
+
return 1
|
|
313
|
+
fi
|
|
314
|
+
|
|
315
|
+
echo "[WORKER-$$] Status confirmed as complete"
|
|
307
316
|
# Clear CURRENT_CANDIDATE_ID before returning to prevent cleanup from interfering
|
|
308
317
|
CURRENT_CANDIDATE_ID=""
|
|
309
318
|
return 0
|
package/lib/evolution_csv.py
CHANGED
|
@@ -416,6 +416,72 @@ class EvolutionCSV:
|
|
|
416
416
|
|
|
417
417
|
return fixed_count
|
|
418
418
|
|
|
419
|
+
def reset_stuck_candidates(self) -> int:
|
|
420
|
+
"""
|
|
421
|
+
Reset 'running' candidates and unknown statuses to 'pending'.
|
|
422
|
+
Should only be called when no workers are active.
|
|
423
|
+
Returns the number of candidates reset.
|
|
424
|
+
"""
|
|
425
|
+
rows = self._read_csv()
|
|
426
|
+
if not rows:
|
|
427
|
+
return 0
|
|
428
|
+
|
|
429
|
+
reset_count = 0
|
|
430
|
+
has_header = rows and rows[0] and rows[0][0].lower() == 'id'
|
|
431
|
+
start_idx = 1 if has_header else 0
|
|
432
|
+
|
|
433
|
+
valid_statuses = {'', 'pending', 'complete', 'failed', 'failed-ai-retry',
|
|
434
|
+
'failed-retry1', 'failed-retry2', 'failed-retry3', 'skipped',
|
|
435
|
+
'failed-parent-missing', 'running'}
|
|
436
|
+
|
|
437
|
+
for i in range(start_idx, len(rows)):
|
|
438
|
+
if len(rows[i]) > 4:
|
|
439
|
+
status = rows[i][4].strip() if rows[i][4] else ''
|
|
440
|
+
candidate_id = rows[i][0] if rows[i] else ''
|
|
441
|
+
|
|
442
|
+
# Reset 'running' when no workers are active
|
|
443
|
+
if status == 'running':
|
|
444
|
+
print(f'[INFO] Resetting stuck running candidate: {candidate_id}', file=sys.stderr)
|
|
445
|
+
rows[i][4] = 'pending'
|
|
446
|
+
reset_count += 1
|
|
447
|
+
# Reset unknown statuses
|
|
448
|
+
elif status not in valid_statuses:
|
|
449
|
+
print(f'[WARN] Resetting unknown status "{status}" to pending: {candidate_id}', file=sys.stderr)
|
|
450
|
+
rows[i][4] = 'pending'
|
|
451
|
+
reset_count += 1
|
|
452
|
+
|
|
453
|
+
if reset_count > 0:
|
|
454
|
+
self._write_csv(rows)
|
|
455
|
+
print(f'[INFO] Reset {reset_count} stuck/unknown candidates to pending', file=sys.stderr)
|
|
456
|
+
|
|
457
|
+
return reset_count
|
|
458
|
+
|
|
459
|
+
def count_stuck_candidates(self) -> int:
|
|
460
|
+
"""
|
|
461
|
+
Count candidates that are stuck (running or have unknown status).
|
|
462
|
+
Returns the number of stuck candidates.
|
|
463
|
+
"""
|
|
464
|
+
rows = self._read_csv()
|
|
465
|
+
if not rows:
|
|
466
|
+
return 0
|
|
467
|
+
|
|
468
|
+
stuck = 0
|
|
469
|
+
has_header = rows and rows[0] and rows[0][0].lower() == 'id'
|
|
470
|
+
start_idx = 1 if has_header else 0
|
|
471
|
+
|
|
472
|
+
valid_statuses = {'', 'pending', 'complete', 'failed', 'failed-ai-retry',
|
|
473
|
+
'failed-retry1', 'failed-retry2', 'failed-retry3', 'skipped',
|
|
474
|
+
'failed-parent-missing'}
|
|
475
|
+
|
|
476
|
+
for i in range(start_idx, len(rows)):
|
|
477
|
+
if len(rows[i]) > 4:
|
|
478
|
+
status = rows[i][4].strip() if rows[i][4] else ''
|
|
479
|
+
# Count running and unknown statuses
|
|
480
|
+
if status == 'running' or (status and status not in valid_statuses):
|
|
481
|
+
stuck += 1
|
|
482
|
+
|
|
483
|
+
return stuck
|
|
484
|
+
|
|
419
485
|
def has_pending_work(self) -> bool:
|
|
420
486
|
"""Check if there are any pending candidates. Used by dispatcher."""
|
|
421
487
|
return self.count_pending_candidates() > 0
|