claude-evolve 1.8.5 → 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.
@@ -583,44 +583,17 @@ while true; do
583
583
  if [[ -f "$FULL_CSV_PATH" && ${#worker_pids[@]} -eq 0 ]]; then
584
584
  echo "[DISPATCHER] Checking for stuck candidates (no active workers)..."
585
585
  "$PYTHON_CMD" -c "
586
- import csv
587
586
  import sys
588
- from pathlib import Path
589
-
590
- csv_file = '$FULL_CSV_PATH'
591
- rows = []
592
-
593
- with open(csv_file, 'r') as f:
594
- rows = list(csv.reader(f))
595
-
596
- reset_count = 0
597
- has_header = rows and rows[0] and rows[0][0].lower() == 'id'
598
- start_idx = 1 if has_header else 0
587
+ sys.path.insert(0, '$SCRIPT_DIR/..')
588
+ from lib.evolution_csv import EvolutionCSV
599
589
 
600
- for i in range(start_idx, len(rows)):
601
- if len(rows[i]) > 4:
602
- status = rows[i][4].strip() if rows[i][4] else ''
603
- candidate_id = rows[i][0] if rows[i] else ''
604
-
605
- # Reset 'running' when no workers are active
606
- if status == 'running':
607
- print(f'[INFO] Resetting stuck running candidate: {candidate_id}', file=sys.stderr)
608
- rows[i][4] = 'pending'
609
- reset_count += 1
610
- # Reset unknown statuses like 'ready'
611
- elif status not in ['', 'pending', 'complete', 'failed', 'failed-ai-retry',
612
- 'failed-retry1', 'failed-retry2', 'failed-retry3', 'skipped',
613
- 'failed-parent-missing']:
614
- print(f'[WARN] Resetting unknown status \"{status}\" to pending: {candidate_id}', file=sys.stderr)
615
- rows[i][4] = 'pending'
616
- reset_count += 1
617
-
618
- if reset_count > 0:
619
- with open(csv_file + '.tmp', 'w', newline='') as f:
620
- csv.writer(f).writerows(rows)
621
- Path(csv_file + '.tmp').rename(csv_file)
622
- print(f'[INFO] Reset {reset_count} stuck/unknown candidates to pending', file=sys.stderr)
623
- " || 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
+ "
624
597
  fi
625
598
  fi
626
599
 
@@ -642,22 +615,17 @@ if reset_count > 0:
642
615
  # Before blocking, do final check for stuck work (immediate, not periodic)
643
616
  echo "[DISPATCHER] Performing final verification for stuck candidates..."
644
617
  stuck_work_count=$("$PYTHON_CMD" -c "
645
- import csv
646
- csv_file = '$FULL_CSV_PATH'
647
- stuck = 0
648
- with open(csv_file, 'r') as f:
649
- reader = csv.reader(f)
650
- next(reader, None) # Skip header
651
- for row in reader:
652
- if len(row) > 4:
653
- status = row[4].strip() if row[4] else ''
654
- # Count running, unknown, and retry statuses
655
- if status in ['running'] or \
656
- (status and status not in ['', 'pending', 'complete', 'failed', 'skipped',
657
- 'failed-ai-retry', 'failed-retry1', 'failed-retry2', 'failed-retry3',
658
- 'failed-parent-missing']):
659
- stuck += 1
660
- 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
661
629
  " 2>/dev/null || echo "0")
662
630
 
663
631
  if [[ $stuck_work_count -gt 0 ]]; then
@@ -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
- " 2>/dev/null || true
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
@@ -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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-evolve",
3
- "version": "1.8.5",
3
+ "version": "1.8.6",
4
4
  "bin": {
5
5
  "claude-evolve": "./bin/claude-evolve",
6
6
  "claude-evolve-main": "./bin/claude-evolve-main",