Every major incident review eventually points back to a Background Script run by a well-meaning engineer at 4pm on a Friday. The temptation is to ban the feature outright, but that just pushes the work into Fix Scripts where it is harder to track. A simple protocol with three habits prevents 95 percent of the damage.
Always count before you change
Before any update, delete, or insert, run the equivalent GlideAggregate to confirm the scope:
var ga = new GlideAggregate('incident');
ga.addEncodedQuery('state=7^sys_updated_on<javascript:gs.daysAgoStart(30)');
ga.addAggregate('COUNT');
ga.query();
ga.next();
gs.print('Will affect: ' + ga.getAggregate('COUNT'));
If the count is unexpected, stop. Do not run the mutation. Roughly half of all “I deleted too much” incidents start with a developer assuming the filter matched a few hundred rows when it actually matched a few hundred thousand.
Wrap in a transaction guard with a kill switch
Use a sys_property as a kill switch:
if (gs.getProperty('background.scripts.allow_writes') !== 'true') {
gs.print('Writes disabled by kill switch.');
} else {
// mutation here
}
Set the property to false by default during business hours. Flip it to true deliberately, run the script, flip it back. Sounds tedious; saves careers.
Never run unbounded loops without setLimit
gr.query() followed by while (gr.next()) will happily process every row in the table. Always call gr.setLimit(1000) first and process in chunks if needed. This is also a habit that makes accidental cross-table joins through reference walks fail loud and fast.
Log everything to a real table, not just gs.print
gs.print output disappears when the session ends. Create a u_background_script_log table with script_name, executed_by, query_count, affected_count, and result_summary. Insert one row per run. When something goes wrong on Tuesday for changes made on Friday, you have a trail.
Restrict the role aggressively
The script_admin role should belong to fewer than five people in production. Everyone else uses sub-prod and submits a script via change request for production execution. The friction is the point. If a script is too urgent to follow the process, escalate it as an incident with a senior engineer pairing.
Use Fix Scripts for anything reusable
If you find yourself running the same Background Script twice, convert it to a Fix Script. Fix Scripts are versioned, reviewable, and execute through a UI page that makes the run history visible. Background Scripts are for one-off exploration, not recurring operations.
Test selects, not updates, in production
If you need to validate something in production and there is no read-only equivalent, write the script as a select with gs.print of what it would change. Have a second engineer review the output before you switch to update mode. Pair-review of mutations is the cheapest insurance policy in operations.
What to do this week: add the kill-switch sys_property, create the audit log table, and require both for every production background script.