|
| 1 | +// Fix Script: Cleanup Orphaned and Stale Workflow Contexts |
| 2 | +// Purpose: Cancel and clean up workflow contexts that are: |
| 3 | +// 1. In "Executing" state for more than 180 days |
| 4 | +// 2. Associated with deleted/invalid parent records |
| 5 | +// 3. Stuck without valid activity states |
| 6 | +// |
| 7 | +// Author: Masthan Sharif Shaik |
| 8 | +// Date: October 2025 |
| 9 | +// Tested on: Zurich, Yokohama |
| 10 | +// |
| 11 | +// IMPORTANT: Test in non-production environment first |
| 12 | +// This script will mark stale workflows as cancelled and prevent re-execution |
| 13 | + |
| 14 | +(function cleanupOrphanedWorkflowContexts() { |
| 15 | + |
| 16 | + // Configuration - adjust these values based on your requirements |
| 17 | + var DAYS_THRESHOLD = 180; // Workflows executing longer than this will be evaluated |
| 18 | + var BATCH_SIZE = 500; // Process in batches to avoid transaction timeouts |
| 19 | + var DRY_RUN = true; // Set to false to actually cancel workflows |
| 20 | + |
| 21 | + // Calculate date threshold |
| 22 | + var thresholdDate = new GlideDateTime(); |
| 23 | + thresholdDate.addDaysLocalTime(-DAYS_THRESHOLD); |
| 24 | + |
| 25 | + var totalProcessed = 0; |
| 26 | + var totalCancelled = 0; |
| 27 | + var orphanedCount = 0; |
| 28 | + |
| 29 | + gs.info('=== Workflow Context Cleanup Started ==='); |
| 30 | + gs.info('Threshold Date: ' + thresholdDate.getDisplayValue()); |
| 31 | + gs.info('Dry Run Mode: ' + DRY_RUN); |
| 32 | + |
| 33 | + // Query for stale executing workflow contexts |
| 34 | + var wfContext = new GlideRecord('wf_context'); |
| 35 | + wfContext.addQuery('state', 'executing'); |
| 36 | + wfContext.addQuery('sys_created_on', '<', thresholdDate); |
| 37 | + wfContext.setLimit(BATCH_SIZE); |
| 38 | + wfContext.query(); |
| 39 | + |
| 40 | + gs.info('Found ' + wfContext.getRowCount() + ' stale workflow contexts to process'); |
| 41 | + |
| 42 | + while (wfContext.next()) { |
| 43 | + totalProcessed++; |
| 44 | + var shouldCancel = false; |
| 45 | + var reason = ''; |
| 46 | + |
| 47 | + // Check if parent record exists |
| 48 | + var tableName = wfContext.getValue('table'); |
| 49 | + var recordId = wfContext.getValue('id'); |
| 50 | + |
| 51 | + if (!tableName || !recordId) { |
| 52 | + shouldCancel = true; |
| 53 | + reason = 'Missing table or record reference'; |
| 54 | + orphanedCount++; |
| 55 | + } else { |
| 56 | + // Verify parent record exists |
| 57 | + var parentRecord = new GlideRecord(tableName); |
| 58 | + if (!parentRecord.get(recordId)) { |
| 59 | + shouldCancel = true; |
| 60 | + reason = 'Parent record no longer exists'; |
| 61 | + orphanedCount++; |
| 62 | + } |
| 63 | + } |
| 64 | + |
| 65 | + |
| 66 | + if (shouldCancel) { |
| 67 | + gs.info('Context: ' + wfContext.getDisplayValue() + |
| 68 | + ' | Age: ' + wfContext.sys_created_on.getDisplayValue() + |
| 69 | + ' | Reason: ' + reason); |
| 70 | + |
| 71 | + if (!DRY_RUN) { |
| 72 | + // Cancel the workflow context |
| 73 | + wfContext.state = 'cancelled'; |
| 74 | + wfContext.setWorkflow(false); // Prevent additional workflows from triggering |
| 75 | + wfContext.update(); |
| 76 | + totalCancelled++; |
| 77 | + } |
| 78 | + } |
| 79 | + } |
| 80 | + |
| 81 | + gs.info('=== Workflow Context Cleanup Complete ==='); |
| 82 | + gs.info('Total Processed: ' + totalProcessed); |
| 83 | + gs.info('Orphaned Workflows Found: ' + orphanedCount); |
| 84 | + gs.info('Workflows Cancelled: ' + (DRY_RUN ? '0 (Dry Run)' : totalCancelled)); |
| 85 | + gs.info('To execute cleanup, set DRY_RUN = false'); |
| 86 | + |
| 87 | +})(); |
0 commit comments