mirror of
https://github.com/leonvanzyl/autocoder.git
synced 2026-02-01 06:53:36 +00:00
fix: address critical issues in PR #75 agent scheduling feature
This commit fixes several issues identified in the agent scheduling feature from PR #75: Frontend Fixes: - Add day boundary handling in timeUtils.ts for timezone conversions - Add utcToLocalWithDayShift/localToUTCWithDayShift functions - Add shiftDaysForward/shiftDaysBackward helpers for bitfield adjustment - Update ScheduleModal to correctly adjust days_of_week when crossing day boundaries during UTC conversion (fixes schedules running on wrong days for users in extreme timezones like UTC+9) Backend Fixes: - Add MAX_SCHEDULES_PER_PROJECT (50) limit to prevent resource exhaustion - Wire up crash recovery callback in scheduler_service._start_agent() - Convert schedules.py endpoints to use context manager for DB sessions - Fix race condition in override creation with atomic delete-then-create - Replace deprecated datetime.utcnow with datetime.now(timezone.utc) - Add DB-level CHECK constraints for Schedule model fields Files Modified: - api/database.py: Add _utc_now helper, CheckConstraint imports, constraints - progress.py: Replace deprecated datetime.utcnow - server/routers/schedules.py: Add context manager, schedule limits - server/services/assistant_database.py: Replace deprecated datetime.utcnow - server/services/scheduler_service.py: Wire crash recovery, fix race condition - ui/src/components/ScheduleModal.tsx: Use day shift functions - ui/src/lib/timeUtils.ts: Add day boundary handling functions Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
@@ -368,6 +368,14 @@ class SchedulerService:
|
||||
logger.info(f"Agent already running for {project_name}, skipping scheduled start")
|
||||
return
|
||||
|
||||
# Register crash callback to enable auto-restart during scheduled windows
|
||||
async def on_status_change(status: str):
|
||||
if status == "crashed":
|
||||
logger.info(f"Crash detected for {project_name}, attempting recovery")
|
||||
await self.handle_crash_during_window(project_name, project_dir)
|
||||
|
||||
manager.add_status_callback(on_status_change)
|
||||
|
||||
logger.info(
|
||||
f"Starting agent for {project_name} "
|
||||
f"(schedule {schedule.id}, yolo={schedule.yolo_mode}, concurrency={schedule.max_concurrency})"
|
||||
@@ -382,6 +390,8 @@ class SchedulerService:
|
||||
logger.info(f"✓ Agent started successfully for {project_name}")
|
||||
else:
|
||||
logger.error(f"✗ Failed to start agent for {project_name}: {msg}")
|
||||
# Remove callback if start failed
|
||||
manager.remove_status_callback(on_status_change)
|
||||
|
||||
async def _stop_agent(self, project_name: str, project_dir: Path):
|
||||
"""Stop the agent for a project."""
|
||||
@@ -457,7 +467,10 @@ class SchedulerService:
|
||||
def _create_override_for_active_schedules(
|
||||
self, project_name: str, project_dir: Path, override_type: str
|
||||
):
|
||||
"""Create overrides for all active schedule windows."""
|
||||
"""Create overrides for all active schedule windows.
|
||||
|
||||
Uses atomic delete-then-create pattern to prevent race conditions.
|
||||
"""
|
||||
from api.database import Schedule, ScheduleOverride, create_database
|
||||
|
||||
try:
|
||||
@@ -479,17 +492,20 @@ class SchedulerService:
|
||||
# Calculate window end time
|
||||
window_end = self._calculate_window_end(schedule, now)
|
||||
|
||||
# Check if override already exists
|
||||
existing = db.query(ScheduleOverride).filter(
|
||||
# Atomic operation: delete any existing overrides of this type
|
||||
# and create a new one in the same transaction
|
||||
deleted = db.query(ScheduleOverride).filter(
|
||||
ScheduleOverride.schedule_id == schedule.id,
|
||||
ScheduleOverride.override_type == override_type,
|
||||
ScheduleOverride.expires_at > now,
|
||||
).first()
|
||||
).delete()
|
||||
|
||||
if existing:
|
||||
continue
|
||||
if deleted:
|
||||
logger.debug(
|
||||
f"Removed {deleted} existing '{override_type}' override(s) "
|
||||
f"for schedule {schedule.id}"
|
||||
)
|
||||
|
||||
# Create override
|
||||
# Create new override
|
||||
override = ScheduleOverride(
|
||||
schedule_id=schedule.id,
|
||||
override_type=override_type,
|
||||
|
||||
Reference in New Issue
Block a user