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:

  1. Jira Administrator permissions - Required to access Automan settings
  2. Automan installed - The app must be installed on your Jira instance
  3. A Script Definition - Create a script in the Definitions tab first

Configuring a Scheduled Script#

Step 1: Create or Edit a Definition#

  1. Navigate to Apps > Automan
  2. Click New Definition or edit an existing definition
  3. Write your JavaScript code

Step 2: Enable Cron#

  1. Click on the Cron tab in the definition form
  2. Check the Enable cron checkbox
  3. 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#

  1. Use appropriate intervals - Don’t schedule scripts to run more often than needed
  2. Handle failures gracefully - Use try/catch to prevent one failure from stopping the entire script
  3. Log progress - Use console.log() to track what your script is doing
  4. Test manually first - Use the Run Script button to test before enabling cron
  5. Consider rate limits - Be mindful of Jira API rate limits in scripts that make many calls
  6. Use pagination - For scripts that process many work items, implement pagination
  7. Store configuration - Use environment variables for configurable values

Troubleshooting#

Script Not Running#

  1. Check that the cron checkbox is enabled
  2. Verify the cron expression is valid (preview shows next executions)
  3. Ensure the definition has saved code (not empty)
  4. Wait for the scheduled time and check the Executions tab

Runs Less Often Than Expected#

  1. Remember the 5-minute minimum interval
  2. Check the preview for skipped executions (shown in red)
  3. Adjust your cron expression if needed

Finding Execution Logs#

  1. Navigate to Apps > Automan
  2. Go to the Executions tab
  3. Filter by type “cron” to see scheduled executions
  4. Click on an execution to view detailed logs