fix(backend): Clean up orphaned schedules without schedule_id

Old scheduled jobs created before schedule_id was added to
GraphExecutionJobArgs have schedule_id=None. When these fail
validation, _handle_graph_validation_error could not unschedule
them, causing them to fire repeatedly and generate ~60K+ Sentry
errors (AUTOGPT-SERVER-6W2 and AUTOGPT-SERVER-6W3).

Fix: Add _cleanup_old_schedules_without_id() which finds schedules
for the graph but only removes those with schedule_id=None (legacy
jobs). This preserves any valid newer schedules the user may have
created, unlike the broader _cleanup_orphaned_schedules_for_graph()
which removes all schedules for a graph.
This commit is contained in:
Bentlybro
2026-02-02 14:06:40 +00:00
parent 1081590384
commit 6b1f0df58c

View File

@@ -193,9 +193,11 @@ async def _handle_graph_validation_error(args: "GraphExecutionJobArgs") -> None:
user_id=args.user_id,
)
else:
logger.error(
f"Unable to unschedule graph: {args.graph_id} as this is an old job with no associated schedule_id please remove manually"
logger.warning(
f"Old scheduled job for graph {args.graph_id} (user {args.user_id}) "
f"has no schedule_id, attempting targeted cleanup"
)
await _cleanup_old_schedules_without_id(args.graph_id, args.user_id)
async def _handle_graph_not_available(
@@ -238,6 +240,35 @@ async def _cleanup_orphaned_schedules_for_graph(graph_id: str, user_id: str) ->
)
async def _cleanup_old_schedules_without_id(graph_id: str, user_id: str) -> None:
"""Remove only schedules that have no schedule_id in their job args.
Unlike _cleanup_orphaned_schedules_for_graph (which removes ALL schedules
for a graph), this only targets legacy jobs created before schedule_id was
added to GraphExecutionJobArgs, preserving any valid newer schedules.
"""
scheduler_client = get_scheduler_client()
schedules = await scheduler_client.get_execution_schedules(
graph_id=graph_id, user_id=user_id
)
for schedule in schedules:
if schedule.schedule_id is not None:
continue
try:
await scheduler_client.delete_schedule(
schedule_id=schedule.id, user_id=user_id
)
logger.info(
f"Cleaned up old schedule {schedule.id} (no schedule_id) "
f"for graph {graph_id}"
)
except Exception:
logger.exception(
f"Failed to delete old schedule {schedule.id} for graph {graph_id}"
)
def cleanup_expired_files():
"""Clean up expired files from cloud storage."""
# Wait for completion