Install
$ agentstack add skill-happy-technologies-llc-happy-platform-skills-script-execution ✓ scanned · ✓ verified, works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
Verified badge
Passed review? Show it. Paste this badge into your README, it links to the public security report.
Reliability & compatibility
Declared compatibility
Compatibility is declared by the source manifest. End-to-end runtime verification is coming, see below.
We're building live execution health for every listing: tool-call success rate, median latency, uptime, and last-checked timestamps, measured, not self-reported. It isn't live yet, so we don't show numbers we can't stand behind.
How agent discovery & health will work →About
Script Execution
Overview
This skill covers safe and effective execution of background scripts in ServiceNow:
- Automated script execution via sys_trigger (the breakthrough method)
- Safe script patterns and templates
- Fix script generation for manual execution
- Error handling and logging best practices
- Debugging and troubleshooting techniques
When to use: When MCP tools don't support the required operation, or when complex data manipulation is needed.
Who should use this: Administrators and developers who need to execute server-side JavaScript in ServiceNow.
Prerequisites
- Roles:
admin(background script execution requires full admin) - Access: systrigger, syslog, sysscript_fix tables
- Knowledge: GlideRecord API, ServiceNow server-side JavaScript
- Caution: Background scripts run with elevated privileges - test carefully
Execution Methods
Method Comparison
| Method | Automation | Speed | Use Case | |--------|------------|-------|----------| | sys_trigger | Full | ~1-2 sec | Preferred for all automated tasks | | UI endpoint | Full | ~1-2 sec | Fallback if trigger fails | | Fix Script | Manual | Variable | Audit trail, scheduled execution | | Scripts - Background | Manual | Immediate | One-time interactive testing |
Procedure
Phase 1: Automated Execution (Recommended)
Step 1.1: Basic Script Execution
The sys_trigger method creates a scheduled job that executes immediately and self-deletes.
Using MCP:
Tool: SN-Execute-Background-Script
Parameters:
script: |
gs.info('Hello from automated script execution!');
// Your logic here
var count = 0;
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.addQuery('priority', 1);
gr.setLimit(10);
gr.query();
while (gr.next()) {
count++;
gs.info('Found P1 incident: ' + gr.number);
}
gs.info('Total P1 incidents found: ' + count);
description: Count active P1 incidents
execution_method: trigger
How It Works:
- Creates a scheduled job in
sys_triggertable - Sets next_action to run immediately
- Trigger executes the script
- Script logs output to system logs
- Trigger auto-deletes after execution
Step 1.2: Verify Execution
Check System Logs:
Tool: SN-Query-Table
Parameters:
table_name: syslog
query: message=*automated script*^sys_created_on>javascript:gs.minutesAgo(5)
fields: message,level,sys_created_on,source
limit: 20
Log Levels: | Level | Method | Purpose | |-------|--------|---------| | 0 | gs.info() | Informational messages | | 1 | gs.warn() | Warning messages | | 2 | gs.error() | Error messages | | 3 | gs.debug() | Debug messages (if enabled) |
Phase 2: Safe Script Patterns
Step 2.1: Query Pattern (Read-Only)
Safe pattern for reading data:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Safe read-only pattern
var results = [];
var gr = new GlideRecord('incident');
gr.addQuery('active', true);
gr.addQuery('priority', 1);
gr.setLimit(100); // Always set a limit
gr.query();
while (gr.next()) {
results.push({
number: gr.number.toString(),
short_description: gr.short_description.toString(),
assigned_to: gr.assigned_to.getDisplayValue(),
created: gr.sys_created_on.toString()
});
}
gs.info('Query Results: ' + JSON.stringify(results, null, 2));
description: Query active P1 incidents (read-only)
Step 2.2: Update Pattern (With Safety Checks)
Pattern for updating records with safety measures:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Safe update pattern with validation
var TABLE = 'incident';
var QUERY = 'active=true^priority=1^assigned_toISEMPTY';
var MAX_UPDATES = 10; // Limit updates per execution
// DRY RUN flag - set to false to actually update
var DRY_RUN = true;
var updated = 0;
var skipped = 0;
var gr = new GlideRecord(TABLE);
gr.addEncodedQuery(QUERY);
gr.setLimit(MAX_UPDATES);
gr.query();
gs.info('Found ' + gr.getRowCount() + ' records to process');
while (gr.next()) {
// Validate before update
if (!gr.canWrite()) {
gs.warn('Cannot write to: ' + gr.number);
skipped++;
continue;
}
if (DRY_RUN) {
gs.info('[DRY RUN] Would update: ' + gr.number);
} else {
gr.work_notes = 'Automated: Escalating unassigned P1';
gr.assignment_group = 'Critical Incidents Team';
gr.update();
gs.info('Updated: ' + gr.number);
updated++;
}
}
gs.info('Summary: Updated=' + updated + ', Skipped=' + skipped + ', DryRun=' + DRY_RUN);
description: Escalate unassigned P1 incidents (with dry run)
Step 2.3: Insert Pattern (With Duplicate Check)
Pattern for creating records safely:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Safe insert pattern with duplicate check
var TABLE = 'sys_properties';
var PROPERTY_NAME = 'custom.automation.enabled';
var PROPERTY_VALUE = 'true';
// Check for existing
var existing = new GlideRecord(TABLE);
existing.addQuery('name', PROPERTY_NAME);
existing.query();
if (existing.next()) {
gs.warn('Property already exists: ' + PROPERTY_NAME + ' = ' + existing.value);
// Update if different
if (existing.value != PROPERTY_VALUE) {
existing.value = PROPERTY_VALUE;
existing.update();
gs.info('Updated property value to: ' + PROPERTY_VALUE);
}
} else {
// Create new
var gr = new GlideRecord(TABLE);
gr.initialize();
gr.name = PROPERTY_NAME;
gr.value = PROPERTY_VALUE;
gr.description = 'Created by automated script';
var sysId = gr.insert();
gs.info('Created property: ' + PROPERTY_NAME + ' (sys_id: ' + sysId + ')');
}
description: Create or update system property
Step 2.4: Delete Pattern (With Confirmation)
Pattern for deleting records with extreme caution:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// DANGEROUS: Delete pattern - use with extreme caution
var TABLE = 'sys_user_preference';
var QUERY = 'user.active=false^sys_created_on= 0; i--) {
var change = changes[i];
var rollback = new GlideRecord(change.table);
rollback.get(change.sys_id);
rollback[change.field] = change.old;
rollback.update();
gs.info('Rolled back: ' + change.table + '.' + change.sys_id);
}
gs.info('Rollback complete');
}
description: Multi-step update with rollback capability
Phase 5: Logging Best Practices
Step 5.1: Structured Logging
Use consistent log formatting:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Structured logging pattern
var Logger = {
scriptName: 'DataMigration',
info: function(message, data) {
var logMessage = '[' + this.scriptName + '] [INFO] ' + message;
if (data) logMessage += ' | Data: ' + JSON.stringify(data);
gs.info(logMessage);
},
warn: function(message, data) {
var logMessage = '[' + this.scriptName + '] [WARN] ' + message;
if (data) logMessage += ' | Data: ' + JSON.stringify(data);
gs.warn(logMessage);
},
error: function(message, error) {
var logMessage = '[' + this.scriptName + '] [ERROR] ' + message;
if (error) logMessage += ' | Error: ' + error.message;
gs.error(logMessage);
},
metric: function(name, value) {
gs.info('[' + this.scriptName + '] [METRIC] ' + name + '=' + value);
}
};
// Usage
Logger.info('Starting migration', { source: 'incident', target: 'x_custom_incident' });
Logger.metric('records_processed', 150);
Logger.warn('Skipped record due to missing field', { number: 'INC0012345' });
Logger.error('Failed to process record', new Error('Invalid reference'));
description: Demonstrate structured logging pattern
Step 5.2: Query Script Logs
Find Script Execution Logs:
Tool: SN-Query-Table
Parameters:
table_name: syslog
query: source=Script execution^sys_created_on>javascript:gs.hoursAgo(1)
fields: message,level,sys_created_on
limit: 100
Filter by Script Name:
Tool: SN-Query-Table
Parameters:
table_name: syslog
query: messageLIKE[DataMigration]^sys_created_on>javascript:gs.hoursAgo(1)
fields: message,level,sys_created_on
limit: 100
Phase 6: Advanced Patterns
Step 6.1: Batch Processing with Progress
For long-running operations:
Tool: SN-Execute-Background-Script
Parameters:
script: |
// Batch processing with progress tracking
var BATCH_SIZE = 100;
var MAX_BATCHES = 10;
var TABLE = 'incident';
var QUERY = 'active=true';
var totalProcessed = 0;
var totalRecords = 0;
var batchNum = 0;
// Get total count
var countGR = new GlideAggregate(TABLE);
countGR.addEncodedQuery(QUERY);
countGR.addAggregate('COUNT');
countGR.query();
if (countGR.next()) {
totalRecords = parseInt(countGR.getAggregate('COUNT'));
}
gs.info('Total records to process: ' + totalRecords);
// Process in batches
while (batchNum ', tracker);
}
gr.orderBy('sys_created_on');
gr.setLimit(RECORDS_PER_EXECUTION);
gr.query();
var lastProcessed = '';
var count = 0;
while (gr.next()) {
// Process record
count++;
lastProcessed = gr.sys_created_on.toString();
}
if (count > 0) {
// Update tracker
gs.setProperty('x_custom.migration.last_processed', lastProcessed);
gs.info('Processed ' + count + ' records. Last: ' + lastProcessed);
// Schedule next execution
if (count == RECORDS_PER_EXECUTION) {
var trigger = new GlideRecord('sys_trigger');
trigger.initialize();
trigger.name = 'Continue Migration ' + new GlideDateTime().getDisplayValue();
trigger.next_action = new GlideDateTime();
trigger.next_action.addSeconds(DELAY_SECONDS);
trigger.script = 'gs.include("MigrationScript");'; // Call script include
trigger.trigger_type = 0; // Run once
trigger.insert();
gs.info('Scheduled next execution in ' + DELAY_SECONDS + ' seconds');
} else {
gs.info('Migration complete!');
gs.setProperty('x_custom.migration.last_processed', ''); // Reset
}
} else {
gs.info('No more records to process');
}
description: Self-scheduling migration script
Script Templates
Template 1: Data Audit
// Data Audit Template
var TABLE = 'incident';
var AUDIT_FIELDS = ['state', 'priority', 'assigned_to'];
var audit = {};
AUDIT_FIELDS.forEach(function(field) {
audit[field] = {};
});
var gr = new GlideRecord(TABLE);
gr.addQuery('active', true);
gr.query();
while (gr.next()) {
AUDIT_FIELDS.forEach(function(field) {
var value = gr.getDisplayValue(field) || '(empty)';
audit[field][value] = (audit[field][value] || 0) + 1;
});
}
gs.info('Audit Results:\n' + JSON.stringify(audit, null, 2));
Template 2: Reference Data Validation
// Reference Validation Template
var TABLE = 'incident';
var REF_FIELD = 'assigned_to';
var REF_TABLE = 'sys_user';
var invalid = [];
var gr = new GlideRecord(TABLE);
gr.addQuery('active', true);
gr.addQuery(REF_FIELD + '.active', false); // Reference to inactive record
gr.query();
while (gr.next()) {
invalid.push({
number: gr.number.toString(),
invalid_ref: gr.getDisplayValue(REF_FIELD)
});
}
gs.info('Found ' + invalid.length + ' records with invalid references');
gs.info(JSON.stringify(invalid, null, 2));
Template 3: Bulk Field Update
// Bulk Field Update Template
var TABLE = 'incident';
var QUERY = 'active=true^category=inquiry';
var UPDATES = {
subcategory: 'general',
contact_type: 'email'
};
var DRY_RUN = true;
var gr = new GlideRecord(TABLE);
gr.addEncodedQuery(QUERY);
gr.query();
var updated = 0;
while (gr.next()) {
if (DRY_RUN) {
gs.info('[DRY RUN] Would update ' + gr.number);
} else {
for (var field in UPDATES) {
gr[field] = UPDATES[field];
}
gr.update();
updated++;
}
}
gs.info((DRY_RUN ? '[DRY RUN] ' : '') + 'Updated ' + updated + ' records');
Tool Usage Summary
| Operation | MCP Tool | Purpose | |-----------|----------|---------| | Execute | SN-Execute-Background-Script | Run server-side JavaScript | | Fix Script | SN-Create-Fix-Script | Create auditable script | | Query Logs | SN-Query-Table | Check execution results | | Create Record | SN-Create-Record | Manual fix script creation |
Best Practices
- Always Use Dry Run First: Test with DRY_RUN = true before real execution
- Set Limits: Always use setLimit() to prevent runaway scripts
- Log Extensively: Use structured logging for debugging
- Handle Errors: Wrap all scripts in try-catch
- Document Scripts: Include author, date, ticket reference in comments
- Avoid Hardcoding: Use variables for sys_ids and queries
- Test in Sub-Production: Never run untested scripts in production
- Use Transactions: Consider rollback patterns for multi-step operations
Troubleshooting
Script Doesn't Execute
Symptom: No output in system logs Causes:
- sys_trigger permissions
- Script syntax error before any logging
Solution:
Tool: SN-Query-Table
Parameters:
table_name: sys_trigger
query: nameLIKEMCP^sys_created_on>javascript:gs.minutesAgo(5)
fields: name,state,next_action,script
Script Times Out
Symptom: Partial execution, timeout error Causes:
- Too many records processed
- Inefficient queries
Solution:
- Add setLimit()
- Use batch processing pattern
- Optimize queries with proper indexes
No Logs Appearing
Symptom: Script runs but no gs.info() output visible Causes:
- Log level filtering
- Looking in wrong time range
Solution:
Tool: SN-Query-Table
Parameters:
table_name: syslog
query: sys_created_on>javascript:gs.minutesAgo(10)
fields: message,level,source,sys_created_on
limit: 50
Related Skills
admin/batch-operations- Bulk record operationsadmin/update-set-management- Track script changesadmin/deployment-workflow- Deploy scripts between instances
References
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: Happy-Technologies-LLC
- Source: Happy-Technologies-LLC/happy-platform-skills
- License: Apache-2.0
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet, be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.