Scheduled Scripts (Cron)#
Run JavaScript scripts on a schedule using cron expressions.
Overview#
Scheduled Scripts allow you to run scripts automatically at specified intervals using cron expressions. This is useful for:
- Generating periodic reports
- Cleaning up old data
- Syncing with external systems
- Sending reminders or notifications
- Performing maintenance tasks
Prerequisites#
Before configuring Scheduled Scripts, ensure you have:
- Jira Administrator permissions - Required to access Automan settings
- Automan installed - The app must be installed on your Jira instance
- A Script Definition - Create a script in the Definitions tab first
Configuring a Scheduled Script#
Step 1: Create or Edit a Definition#
- Navigate to
Apps>Automan - Click New Definition or edit an existing definition
- Write your JavaScript code
Step 2: Enable Cron#
- Click on the Cron tab in the definition form
- Check the Enable cron checkbox
- Enter a cron expression
Step 3: Preview Schedule#
After entering a cron expression, Automan shows the next 20 scheduled executions:
Step 4: Save#
Click Save to activate the scheduled script.
Cron Expression Format#
Cron expressions define when your script should run. The format is:
* * * * * *
┬ ┬ ┬ ┬ ┬ ┬
│ │ │ │ │ │
│ │ │ │ │ └─ day of week (0-7, 1L-7L) (0 or 7 is Sunday)
│ │ │ │ └────── month (1-12, JAN-DEC)
│ │ │ └─────────── day of month (1-31, L)
│ │ └──────────────── hour (0-23)
│ └───────────────────── minute (0-59)
└────────────────────────── second (0-59, optional)Special Characters#
| Character | Description | Example |
|---|---|---|
* |
Any value | * * * * * (every minute) |
, |
Value list separator | 1,2,3 * * * * (1st, 2nd, 3rd minute) |
- |
Range of values | 1-5 * * * * (minutes 1 through 5) |
/ |
Step values | */5 * * * * (every 5th minute) |
L |
Last day of month/week | 0 0 L * * (last day of month) |
# |
Nth day of month | 0 0 * * 1#1 (first Monday) |
Field Values#
| Field | Values | Aliases |
|---|---|---|
| second | 0-59 | - |
| minute | 0-59 | - |
| hour | 0-23 | - |
| day of month | 1-31 | - |
| month | 1-12 | JAN-DEC |
| day of week | 0-7 | SUN-SAT (0 or 7 is Sunday) |
Predefined Expressions#
| Expression | Description |
|---|---|
@yearly |
Once a year at midnight of January 1 |
@monthly |
Once a month at midnight of first day |
@weekly |
Once a week at midnight on Sunday |
@daily |
Once a day at midnight |
@hourly |
Once an hour at the beginning |
@weekdays |
Every weekday at midnight |
@weekends |
Every weekend at midnight |
Common Cron Examples#
| Expression | Description |
|---|---|
0 9 * * * |
Every day at 9:00 AM UTC |
0 9 * * 1-5 |
Every weekday at 9:00 AM UTC |
0 */2 * * * |
Every 2 hours |
30 8 * * 1 |
Every Monday at 8:30 AM UTC |
0 0 1 * * |
First day of every month at midnight |
0 0 L * * |
Last day of every month at midnight |
0 9,17 * * 1-5 |
Weekdays at 9:00 AM and 5:00 PM UTC |
0,30 8-16 * * * |
Every 30 minutes from 8 AM to 4:30 PM UTC |
Important: Minimum Interval#
Automan uses Atlassian Forge’s scheduled trigger infrastructure, which has a minimum interval of 5 minutes.
This means:
- Cron expressions that would run more frequently than every 5 minutes will skip some executions
- The preview shows which scheduled times will be skipped (shown in red)
For example, with * * * * * (every minute), only every 5th minute will actually execute.
Context Variables#
When your scheduled script executes, the event object contains:
{
type: "cron",
cronExpression: "0 9 * * *", // The cron expression
scheduledTime: "2024-01-15T09:00:00.000Z" // When this execution was scheduled
}Timezone#
All cron expressions are evaluated in UTC timezone. Plan your schedules accordingly.
To convert from your local time to UTC:
- US Eastern (EST): UTC - 5 hours (UTC - 4 during daylight saving)
- US Pacific (PST): UTC - 8 hours (UTC - 7 during daylight saving)
- Central European (CET): UTC + 1 hour (UTC + 2 during daylight saving)
- UK (GMT/BST): UTC + 0 (UTC + 1 during daylight saving)
Examples#
Example 1: Daily Report at 9 AM UTC#
Cron: 0 9 * * *
console.log("Generating daily report at:", event.scheduledTime);
// Find all work items created yesterday
const yesterday = new Date();
yesterday.setDate(yesterday.getDate() - 1);
const dateStr = yesterday.toISOString().split('T')[0];
const jql = `created >= "${dateStr}" AND created < "${dateStr}" + 1d`;
const response = await requestJira(
route`/rest/api/3/search/jql?jql=${encodeURIComponent(jql)}&maxResults=0`
);
const result = await response.json();
console.log(`Work items created yesterday: ${result.total}`);
// You could store this in a custom field, create a confluence page,
// or send to an external reporting system
Example 2: Weekly Stale Work Item Check#
Cron: 0 9 * * 1 (Every Monday at 9 AM UTC)
console.log("Checking for stale work items...");
// Find work items not updated in 14+ days
const jql = `status != Done AND updated <= -14d ORDER BY updated ASC`;
const response = await requestJira(
route`/rest/api/3/search/jql?jql=${encodeURIComponent(jql)}&maxResults=50`
);
const result = await response.json();
console.log(`Found ${result.total} stale work items`);
// Add a comment to each stale work item
for (const issue of result.issues) {
await requestJira(
route`/rest/api/3/issue/${issue.key}/comment`,
{
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
body: {
type: "doc",
version: 1,
content: [{
type: "paragraph",
content: [{
type: "text",
text: "This work item has not been updated in over 14 days. Please review and update status."
}]
}]
}
})
}
);
console.log(`Reminded on: ${issue.key}`);
}Example 3: Monthly Sprint Cleanup#
Cron: 0 0 1 * * (First day of each month at midnight)
console.log("Running monthly sprint cleanup...");
// Find completed work items without resolution
const jql = `status = Done AND resolution = EMPTY`;
const response = await requestJira(
route`/rest/api/3/search/jql?jql=${encodeURIComponent(jql)}&maxResults=100`
);
const result = await response.json();
console.log(`Found ${result.total} work items needing resolution`);
// Set resolution to "Done" for each
for (const issue of result.issues) {
await requestJira(
route`/rest/api/3/issue/${issue.key}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
fields: {
resolution: { name: "Done" }
}
})
}
);
console.log(`Set resolution on: ${issue.key}`);
}Example 4: Hourly SLA Check#
Cron: 0 * * * * (Every hour)
console.log("Checking SLA compliance...");
// Find high priority work items older than 4 hours without assignee
const jql = `priority = Highest AND assignee = EMPTY AND created <= -4h AND status != Done`;
const response = await requestJira(
route`/rest/api/3/search/jql?jql=${encodeURIComponent(jql)}&maxResults=50`
);
const result = await response.json();
if (result.total > 0) {
console.log(`WARNING: ${result.total} high priority work items unassigned for 4+ hours`);
// Assign to default responder
const defaultAssignee = process.env.DEFAULT_RESPONDER_ID;
for (const issue of result.issues) {
await requestJira(
route`/rest/api/3/issue/${issue.key}`,
{
method: "PUT",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
fields: {
assignee: { accountId: defaultAssignee }
}
})
}
);
console.log(`Auto-assigned: ${issue.key}`);
}
} else {
console.log("All high priority work items are assigned");
}Example 5: End of Day Summary#
Cron: 0 17 * * 1-5 (Every weekday at 5 PM UTC)
console.log("Generating end-of-day summary...");
const today = new Date().toISOString().split('T')[0];
// Count work items by status changed today
const statusStats = {};
const jql = `statusCategory changed DURING ("${today}", "${today}")`;
const response = await requestJira(
route`/rest/api/3/search/jql?jql=${encodeURIComponent(jql)}&maxResults=200`
);
const result = await response.json();
for (const issue of result.issues) {
const status = issue.fields.status.name;
statusStats[status] = (statusStats[status] || 0) + 1;
}
console.log("Work items by current status:");
for (const [status, count] of Object.entries(statusStats)) {
console.log(` ${status}: ${count}`);
}
console.log(`Total work items with status changes: ${result.total}`);Best Practices#
- Use appropriate intervals - Don’t schedule scripts to run more often than needed
- Handle failures gracefully - Use try/catch to prevent one failure from stopping the entire script
- Log progress - Use
console.log()to track what your script is doing - Test manually first - Use the Run Script button to test before enabling cron
- Consider rate limits - Be mindful of Jira API rate limits in scripts that make many calls
- Use pagination - For scripts that process many work items, implement pagination
- Store configuration - Use environment variables for configurable values
Troubleshooting#
Script Not Running#
- Check that the cron checkbox is enabled
- Verify the cron expression is valid (preview shows next executions)
- Ensure the definition has saved code (not empty)
- Wait for the scheduled time and check the Executions tab
Runs Less Often Than Expected#
- Remember the 5-minute minimum interval
- Check the preview for skipped executions (shown in red)
- Adjust your cron expression if needed
Finding Execution Logs#
- Navigate to
Apps>Automan - Go to the Executions tab
- Filter by type “cron” to see scheduled executions
- Click on an execution to view detailed logs