Skip to main content
Safe Settings can be configured to run periodic synchronization jobs using cron scheduling. This ensures that even if webhook events are missed or manual changes are made, your repositories will eventually converge back to the desired state defined in your configuration.

Why Scheduled Sync?

Webhook events are not always guaranteed to be delivered. Scheduled synchronization provides:
  • Drift prevention: Catches manual changes that bypass webhook detection
  • Reliability: Ensures settings are eventually consistent even if webhooks fail
  • Periodic auditing: Regular checks that all repositories remain compliant
  • Recovery: Automatically corrects any configuration drift over time
Source: README.md:418-421

CRON Configuration

Scheduled sync is configured using the CRON environment variable with node-cron syntax.

Node-Cron Syntax

# ┌────────────── second (optional)
# │ ┌──────────── minute
# │ │ ┌────────── hour
# │ │ │ ┌──────── day of month
# │ │ │ │ ┌────── month
# │ │ │ │ │ ┌──── day of week
# │ │ │ │ │ │
# │ │ │ │ │ │
# * * * * * *
Source: index.js:649-659 and README.md:511-523

Configuration Examples

CRON="* * * * *"
Setting a very frequent schedule (e.g., every minute) can cause rate limiting issues with the GitHub API, especially for organizations with many repositories. Consider your organization size when choosing a schedule.

How Scheduled Sync Works

When the cron schedule triggers, Safe Settings performs a full synchronization across all repositories in the organization.
1
Cron trigger activates
2
The node-cron scheduler fires at the specified interval.
3
Fetch installations
4
Safe Settings retrieves all GitHub App installations.
5
Load configurations
6
Fetches the latest configuration from the admin repository:
7
  • .github/settings.yml (org-level)
  • .github/suborgs/*.yml (suborg-level)
  • .github/repos/*.yml (repo-level)
  • 8
    Process all repositories
    9
    Iterates through all repositories in the installation and applies settings:
    10
  • Merges configurations (org → suborg → repo)
  • Compares with current GitHub settings
  • Makes API calls only if changes are detected
  • 11
    Create check run
    12
    Creates a check run in the admin repository with:
    13
  • Success/failure status
  • List of repositories processed
  • Any errors encountered
  • Source: index.js:660-664
    if (process.env.CRON) {
      cron.schedule(process.env.CRON, () => {
        robot.log.debug('running a task every minute')
        syncInstallation()
      })
    }
    

    Sync Installation Function

    The syncInstallation function is the core of scheduled synchronization. Source: index.js:226-248
    async function syncInstallation (nop = false) {
      robot.log.trace('Fetching installations')
      const github = await robot.auth()
    
      const installations = await github.paginate(
        github.apps.listInstallations.endpoint.merge({ per_page: 100 })
      )
    
      if (installations.length > 0) {
        const installation = installations[0]
        const github = await robot.auth(installation.id)
        const context = {
          payload: { installation },
          octokit: github,
          log: robot.log,
          repo: () => { 
            return { repo: env.ADMIN_REPO, owner: installation.account.login } 
          }
        }
        return syncAllSettings(nop, context)
      }
      return null
    }
    

    Key Features

    • Installation-aware: Works with GitHub App installations
    • Context creation: Builds a context object similar to webhook events
    • Full sync: Calls syncAllSettings() to process all repositories
    • NOP mode support: Can run in dry-run mode for testing

    Performance Considerations

    Scheduled sync is designed to handle large organizations efficiently:

    Rate Limiting

    • Probot automatically handles GitHub API rate limits and abuse limits
    • Requests are throttled and retried automatically
    • Consider using less frequent schedules for large organizations
    Source: README.md:311-316

    Selective Loading

    • Configs are only loaded when needed
    • Uses GitHub’s Tree API for efficient file listing
    • Caches file contents with ETags to minimize data transfer
    Source: lib/settings.js:838-891

    Smart Comparison

    • Compares desired state with current GitHub settings
    • Only makes API calls when actual changes are detected
    • Skips repositories that are already in compliance
    Source: README.md:318-417

    Token Lifetime

    GitHub App tokens expire after 1 hour. Scheduled sync operations must complete within this timeframe. For very large organizations, consider:
    • Optimizing your configuration structure
    • Using fewer repositories per sync
    • Splitting into multiple installations
    Source: README.md:308-310

    Monitoring Scheduled Syncs

    Each scheduled sync creates a check run in the admin repository, allowing you to monitor:
    • Execution time: When the sync ran
    • Success/failure: Whether all settings were applied successfully
    • Errors: Detailed error messages for any failures
    • Changes applied: Summary of modifications made
    See Monitoring for details on check runs.

    Scheduled Sync vs Webhook Events

    AspectWebhook EventsScheduled Sync
    TriggerImmediate (on change)Periodic (cron schedule)
    LatencySecondsMinutes to hours
    ReliabilityDepends on webhook deliveryGuaranteed if app is running
    API UsageMinimal (only affected repos)Higher (checks all repos)
    Use CaseReal-time enforcementDrift prevention & recovery
    ScopeSpecific repos/eventsAll repositories

    Best Practices

    For most organizations:
    • Small (< 50 repos): Every 15-30 minutes
    • Medium (50-500 repos): Every 1-2 hours
    • Large (> 500 repos): Every 4-24 hours
    Consider API rate limits and the criticality of drift prevention.
    Before enabling scheduled sync in production:
    syncInstallation(true) // nop=true for dry-run
    
    This validates configurations without making actual changes.
    Scheduled syncs create check runs in the admin repository. Monitor these for:
    • Recurring errors
    • Performance degradation
    • Unexpected changes
    Use both approaches:
    • Webhooks for immediate enforcement
    • Scheduled sync as a safety net for missed events or drift
    Set LOG_LEVEL to balance visibility and log volume:
    LOG_LEVEL=info  # For production
    LOG_LEVEL=debug # For troubleshooting
    

    Environment Variables

    Related environment variables for scheduled sync:
    VariableDefaultDescription
    CRON(not set)Node-cron schedule expression
    LOG_LEVEL(varies)Logging verbosity (trace, debug, info, warn, error)
    ADMIN_REPOadminRepository containing configurations
    Source: README.md:507-551

    Disabling Scheduled Sync

    To disable scheduled synchronization, simply remove or comment out the CRON environment variable:
    .env
    # CRON="0 * * * *"  # Disabled
    
    Safe Settings will continue to respond to webhook events but will not perform periodic syncs.

    Example: Production Configuration

    A typical production setup for a medium-sized organization:
    .env
    # Sync every 2 hours
    CRON="0 */2 * * *"
    
    # Production logging
    LOG_LEVEL=info
    
    # Admin repository name
    ADMIN_REPO=safe-settings-config
    
    This configuration:
    • Runs sync 12 times per day
    • Balances drift prevention with API usage
    • Provides adequate logging for monitoring
    • Uses a custom admin repository name

    Build docs developers (and LLMs) love