$npx -y skills add Impertio-Studio/Frappe_Claude_Skill_Package --skill frappe-syntax-schedulerUse when configuring scheduler events and background jobs in Frappe/ERPNext v14/v15/v16. Covers scheduler_events in hooks.py, frappe.enqueue() for async jobs, queue configuration, job deduplication, error handling, and monitoring. Keywords: scheduler, background job, cron, RQ wor
| 1 | # Frappe Scheduler & Background Jobs |
| 2 | |
| 3 | Deterministic syntax reference for Frappe scheduler events and background job processing via Redis Queue (RQ). |
| 4 | |
| 5 | ## Decision Tree |
| 6 | |
| 7 | ``` |
| 8 | Need periodic execution? |
| 9 | ├─ Fixed interval (hourly/daily/weekly/monthly) → scheduler_events in hooks.py |
| 10 | ├─ Custom cron schedule → scheduler_events.cron in hooks.py |
| 11 | ├─ User-configurable interval → Scheduled Job Type DocType |
| 12 | └─ No, triggered by user/event |
| 13 | ├─ Run method on a specific document → frappe.enqueue_doc() |
| 14 | ├─ Run standalone function async → frappe.enqueue() |
| 15 | └─ Run from controller on self → self.queue_action() |
| 16 | ``` |
| 17 | |
| 18 | ## Quick Reference: Scheduler Events (hooks.py) |
| 19 | |
| 20 | ```python |
| 21 | # hooks.py — ALWAYS run bench migrate after changes |
| 22 | scheduler_events = { |
| 23 | # Standard events (default queue) |
| 24 | "all": ["myapp.tasks.every_tick"], # Every tick [v14: 240s, v15+: 60s] |
| 25 | "hourly": ["myapp.tasks.hourly_task"], |
| 26 | "daily": ["myapp.tasks.daily_task"], |
| 27 | "weekly": ["myapp.tasks.weekly_task"], |
| 28 | "monthly": ["myapp.tasks.monthly_task"], |
| 29 | |
| 30 | # Long queue events (for heavy processing) |
| 31 | "hourly_long": ["myapp.tasks.hourly_heavy"], |
| 32 | "daily_long": ["myapp.tasks.daily_heavy"], |
| 33 | "weekly_long": ["myapp.tasks.weekly_heavy"], |
| 34 | "monthly_long": ["myapp.tasks.monthly_heavy"], |
| 35 | |
| 36 | # Cron events (croniter-compatible syntax) |
| 37 | "cron": { |
| 38 | "*/15 * * * *": ["myapp.tasks.every_15_min"], |
| 39 | "0 9 * * 1-5": ["myapp.tasks.weekday_9am"], |
| 40 | "0 0 1 * *": ["myapp.tasks.first_of_month"], |
| 41 | } |
| 42 | } |
| 43 | ``` |
| 44 | |
| 45 | **CRITICAL**: ALWAYS run `bench migrate` after ANY change to scheduler_events. Without it, changes are NOT applied. |
| 46 | |
| 47 | ## Scheduler Event Types |
| 48 | |
| 49 | | Event | Frequency | Queue | Use Case | |
| 50 | |-------|-----------|-------|----------| |
| 51 | | `all` | Every tick [v14: 4min, v15+: 60s] | default | Frequent polling | |
| 52 | | `hourly` | Once per hour | default | Sync, cleanup | |
| 53 | | `daily` | Once per day | default | Reports, summaries | |
| 54 | | `weekly` | Once per week | default | Archival | |
| 55 | | `monthly` | Once per month | default | Billing, statements | |
| 56 | | `hourly_long` | Once per hour | **long** | Heavy sync | |
| 57 | | `daily_long` | Once per day | **long** | Large exports | |
| 58 | | `weekly_long` | Once per week | **long** | Data warehousing | |
| 59 | | `monthly_long` | Once per month | **long** | Annual reports | |
| 60 | | `cron` | Custom schedule | configurable | Any custom timing | |
| 61 | |
| 62 | ## Cron Syntax |
| 63 | |
| 64 | ``` |
| 65 | ┌───────────── minute (0-59) |
| 66 | │ ┌───────────── hour (0-23) |
| 67 | │ │ ┌───────────── day of month (1-31) |
| 68 | │ │ │ ┌───────────── month (1-12) |
| 69 | │ │ │ │ ┌───────────── day of week (0-6, Sunday=0) |
| 70 | │ │ │ │ │ |
| 71 | * * * * * |
| 72 | ``` |
| 73 | |
| 74 | | Symbol | Meaning | Example | |
| 75 | |--------|---------|---------| |
| 76 | | `*` | Any value | `* * * * *` = every minute | |
| 77 | | `,` | List | `1,15 * * * *` = minute 1 and 15 | |
| 78 | | `-` | Range | `0 9-17 * * *` = hours 9 through 17 | |
| 79 | | `/` | Interval | `*/10 * * * *` = every 10 minutes | |
| 80 | |
| 81 | Common patterns: |
| 82 | - Every 5 min: `*/5 * * * *` |
| 83 | - Weekdays at 9:00: `0 9 * * 1-5` |
| 84 | - Monday at 8:00: `0 8 * * 1` |
| 85 | - Business hours hourly: `0 9-17 * * 1-5` |
| 86 | |
| 87 | ## Quick Reference: frappe.enqueue() |
| 88 | |
| 89 | ```python |
| 90 | frappe.enqueue( |
| 91 | method, # REQUIRED: function or "dotted.module.path" |
| 92 | queue="default", # "short", "default", "long", or custom |
| 93 | timeout=None, # Override queue timeout (seconds) |
| 94 | is_async=True, # False = run synchronously (skip worker) |
| 95 | now=False, # True = run via frappe.call() directly |
| 96 | job_id=None, # [v15+] Unique ID for deduplication |
| 97 | enqueue_after_commit=False, # Wait for DB commit before enqueue |
| 98 | at_front=False, # Place at front of queue |
| 99 | on_success=None, # Success callback |
| 100 | on_failure=None, # Failure callback |
| 101 | **kwargs # Arguments passed to method |
| 102 | ) |
| 103 | ``` |
| 104 | |
| 105 | ## Queue Types |
| 106 | |
| 107 | | Queue | Default Timeout | Use When | |
| 108 | |-------|-----------------|----------| |
| 109 | | `short` | 300s (5 min) | Task < 30 seconds | |
| 110 | | `default` | 300s (5 min) | Task 30s - 5 min | |
| 111 | | `long` | 1500s (25 min) | Task 5 - 25 min | |
| 112 | | `long` + custom timeout | user-defined | Task > 25 min | |
| 113 | |
| 114 | ```python |
| 115 | # Short queue — quick status update |
| 116 | frappe.enqueue("myapp.tasks.update_status", queue="short", doc=doc.name) |
| 117 | |
| 118 | # Long queue — heavy report generation |
| 119 | frappe.enqueue("myapp.tasks.generate_report", queue="long", timeout=3600) |
| 120 | ``` |
| 121 | |
| 122 | ## frappe.enqueue_doc() |
| 123 | |
| 124 | Enqueue a con |