{"id":"convex-cron-jobs","name":"convex-cron-jobs","summary":"バックグラウンドタスクのスケジュール機能パターン(インターバルスケジューリング、クロン式、ジョブモニタリング、再試行戦略、長期実行タスクのベストプラクティスなどが含まれます)","body":"# Convex Cron Jobs\n\nSchedule recurring functions for background tasks, cleanup jobs, data syncing, and automated workflows in Convex applications.\n\n## Documentation Sources\n\nBefore implementing, do not assume; fetch the latest documentation:\n\n- Primary: https://docs.convex.dev/scheduling/cron-jobs\n- Scheduling Overview: https://docs.convex.dev/scheduling\n- Scheduled Functions: https://docs.convex.dev/scheduling/scheduled-functions\n- For broader context: https://docs.convex.dev/llms.txt\n\n## Instructions\n\n### Cron Jobs Overview\n\nConvex cron jobs allow you to schedule functions to run at regular intervals or specific times. Key features:\n\n- Run functions on a fixed schedule\n- Support for interval-based and cron expression scheduling\n- Automatic retries on failure\n- Monitoring via the Convex dashboard\n\n### Basic Cron Setup\n\n```typescript\n// convex/crons.ts\nimport { cronJobs } from \"convex/server\";\nimport { internal } from \"./_generated/api\";\n\nconst crons = cronJobs();\n\n// Run every hour\ncrons.interval(\n  \"cleanup expired sessions\",\n  { hours: 1 },\n  internal.tasks.cleanupExpiredSessions,\n  {}\n);\n\n// Run every day at midnight UTC\ncrons.cron(\n  \"daily report\",\n  \"0 0 * * *\",\n  internal.reports.generateDailyReport,\n  {}\n);\n\nexport default crons;\n```\n\n### Interval-Based Scheduling\n\nUse `crons.interval` for simple recurring tasks:\n\n```typescript\n// convex/crons.ts\nimport { cronJobs } from \"convex/server\";\nimport { internal } from \"./_generated/api\";\n\nconst crons = cronJobs();\n\n// Every 5 minutes\ncrons.interval(\n  \"sync external data\",\n  { minutes: 5 },\n  internal.sync.fetchExternalData,\n  {}\n);\n\n// Every 2 hours\ncrons.interval(\n  \"cleanup temp files\",\n  { hours: 2 },\n  internal.files.cleanupTempFiles,\n  {}\n);\n\n// Every 30 seconds (minimum interval)\ncrons.interval(\n  \"health check\",\n  { seconds: 30 },\n  internal.monitoring.healthCheck,\n  {}\n);\n\nexport default crons;\n```\n\n### Cron Expression Scheduling\n\nUse `crons.cron` for precise scheduling with cron expressions:\n\n```typescript\n// convex/crons.ts\nimport { cronJobs } from \"convex/server\";\nimport { internal } from \"./_generated/api\";\n\nconst crons = cronJobs();\n\n// Every day at 9 AM UTC\ncrons.cron(\n  \"morning notifications\",\n  \"0 9 * * *\",\n  internal.notifications.sendMorningDigest,\n  {}\n);\n\n// Every Monday at 8 AM UTC\ncrons.cron(\n  \"weekly summary\",\n  \"0 8 * * 1\",\n  internal.reports.generateWeeklySummary,\n  {}\n);\n\n// First day of every month at midnight\ncrons.cron(\n  \"monthly billing\",\n  \"0 0 1 * *\",\n  internal.billing.processMonthlyBilling,\n  {}\n);\n\n// Every 15 minutes\ncrons.cron(\n  \"frequent sync\",\n  \"*/15 * * * *\",\n  internal.sync.syncData,\n  {}\n);\n\nexport default crons;\n```\n\n### Cron Expression Reference\n\n```\n┌───────────── minute (0-59)\n│ ┌───────────── hour (0-23)\n│ │ ┌───────────── day of month (1-31)\n│ │ │ ┌───────────── month (1-12)\n│ │ │ │ ┌───────────── day of week (0-6, Sunday=0)\n│ │ │ │ │\n* * * * *\n```\n\nCommon patterns:\n- `* * * * *` - Every minute\n- `0 * * * *` - Every hour\n- `0 0 * * *` - Every day at midnight\n- `0 0 * * 0` - Every Sunday at midnight\n- `0 0 1 * *` - First day of every month\n- `*/5 * * * *` - Every 5 minutes\n- `0 9-17 * * 1-5` - Every hour from 9 AM to 5 PM, Monday through Friday\n\n### Internal Functions for Crons\n\nCron jobs should call internal functions for security:\n\n```typescript\n// convex/tasks.ts\nimport { internalMutation, internalQuery } from \"./_generated/server\";\nimport { v } from \"convex/values\";\n\n// Cleanup expired sessions\nexport const cleanupExpiredSessions = internalMutation({\n  args: {},\n  returns: v.number(),\n  handler: async (ctx) => {\n    const oneHourAgo = Date.now() - 60 * 60 * 1000;\n    \n    const expiredSessions = await ctx.db\n      .query(\"sessions\")\n      .withIndex(\"by_lastActive\")\n      .filter((q) => q.lt(q.field(\"lastActive\"), oneHourAgo))\n      .collect();\n\n    for (const session of expiredSessions) {\n      await ctx.db.delete(session._id);\n    }\n\n    return expiredSessions.length;\n  },\n});\n\n// Process pending tasks\nexport const processPendingTasks = internalMutation({\n  args: {},\n  returns: v.null(),\n  handler: async (ctx) => {\n    const pendingTasks = await ctx.db\n      .query(\"tasks\")\n      .withIndex(\"by_status\", (q) => q.eq(\"status\", \"pending\"))\n      .take(100);\n\n    for (const task of pendingTasks) {\n      await ctx.db.patch(task._id, {\n        status: \"processing\",\n        startedAt: Date.now(),\n      });\n      \n      // Schedule the actual processing\n      await ctx.scheduler.runAfter(0, internal.tasks.processTask, {\n        taskId: task._id,\n      });\n    }\n\n    return null;\n  },\n});\n```\n\n### Cron Jobs with Arguments\n\nPass static arguments to cron jobs:\n\n```typescript\n// convex/crons.ts\nimport { cronJobs } from \"convex/server\";\nimport { internal } from \"./_generated/api\";\n\nconst crons = cronJobs();\n\n// Different cleanup intervals for different types\ncrons.interval(\n  \"cleanup temp files\",\n  { hours: 1 },\n  internal.cleanup.cleanupByType,\n  { fileType: \"temp\", maxAge: 3600000 }\n);\n\ncrons.interval(\n  \"cleanup cache files\",\n  { hours: 24 },\n  internal.cleanup.cleanupByType,\n  { fileType: \"cache\", maxAge: 86400000 }\n);\n\nexport default crons;\n```\n\n```typescript\n// convex/cleanup.ts\nimport { internalMutation } from \"./_generated/server\";\nimport { v } from \"convex/values\";\n\nexport const cleanupByType = internalMutation({\n  args: {\n    fileType: v.string(),\n    maxAge: v.number(),\n  },\n  returns: v.number(),\n  handler: async (ctx, args) => {\n    const cutoff = Date.now() - args.maxAge;\n    \n    const oldFiles = await ctx.db\n      .query(\"files\")\n      .withIndex(\"by_type_and_created\", (q) => \n        q.eq(\"type\", args.fileType).lt(\"createdAt\", cutoff)\n      )\n      .collect();\n\n    for (const file of oldFiles) {\n      await ctx.storage.delete(file.storageId);\n      await ctx.db.delete(file._id);\n    }\n\n    return oldFiles.length;\n  },\n});\n```\n\n### Monitoring and Logging\n\nAdd logging to track cron job execution:\n\n```typescript\n// convex/tasks.ts\nimport { internalMutation } from \"./_generated/server\";\nimport { v } from \"convex/values\";\n\nexport const cleanupWithLogging = internalMutation({\n  args: {},\n  returns: v.null(),\n  handler: async (ctx) => {\n    const startTime = Date.now();\n    let processedCount = 0;\n    let errorCount = 0;\n\n    try {\n      const expiredItems = await ctx.db\n        .query(\"items\")\n        .withIndex(\"by_expiresAt\")\n        .filter((q) => q.lt(q.field(\"expiresAt\"), Date.now()))\n        .collect();\n\n      for (const item of expiredItems) {\n        try {\n          await ctx.db.delete(item._id);\n          processedCount++;\n        } catch (error) {\n          errorCount++;\n          console.error(`Failed to delete item ${item._id}:`, error);\n        }\n      }\n\n      // Log job completion\n      await ctx.db.insert(\"cronLogs\", {\n        jobName: \"cleanup\",\n        startTime,\n        endTime: Date.now(),\n        duration: Date.now() - startTime,\n        processedCount,\n        errorCount,\n        status: errorCount === 0 ? \"success\" : \"partial\",\n      });\n    } catch (error) {\n      // Log job failure\n      await ctx.db.insert(\"cronLogs\", {\n        jobName: \"cleanup\",\n        startTime,\n        endTime: Date.now(),\n        duration: Date.now() - startTime,\n        processedCount,\n        errorCount,\n        status: \"failed\",\n        error: String(error),\n      });\n      throw error;\n    }\n\n    return null;\n  },\n});\n```\n\n### Batching for Large Datasets\n\nHandle large datasets in batches to avoid timeouts:\n\n```typescript\n// convex/tasks.ts\nimport { internalMutation } from \"./_generated/server\";\nimport { internal } from \"./_generated/api\";\nimport { v } from \"convex/values\";\n\nconst BATCH_SIZE = 100;\n\nexport const processBatch = internalMutation({\n  args: {\n    cursor: v.optional(v.string()),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    const result = await ctx.db\n      .query(\"items\")\n      .withIndex(\"by_status\", (q) => q.eq(\"status\", \"pending\"))\n      .paginate({ numItems: BATCH_SIZE, cursor: args.cursor ?? null });\n\n    for (const item of result.page) {\n      await ctx.db.patch(item._id, {\n        status: \"processed\",\n        processedAt: Date.now(),\n      });\n    }\n\n    // Schedule next batch if there are more items\n    if (!result.isDone) {\n      await ctx.scheduler.runAfter(0, internal.tasks.processBatch, {\n        cursor: result.continueCursor,\n      });\n    }\n\n    return null;\n  },\n});\n```\n\n### External API Calls in Crons\n\nUse actions for external API calls:\n\n```typescript\n// convex/sync.ts\n\"use node\";\n\nimport { internalAction } from \"./_generated/server\";\nimport { internal } from \"./_generated/api\";\nimport { v } from \"convex/values\";\n\nexport const syncExternalData = internalAction({\n  args: {},\n  returns: v.null(),\n  handler: async (ctx) => {\n    // Fetch from external API\n    const response = await fetch(\"https://api.example.com/data\", {\n      headers: {\n        Authorization: `Bearer ${process.env.API_KEY}`,\n      },\n    });\n\n    if (!response.ok) {\n      throw new Error(`API request failed: ${response.status}`);\n    }\n\n    const data = await response.json();\n\n    // Store the data using a mutation\n    await ctx.runMutation(internal.sync.storeExternalData, {\n      data,\n      syncedAt: Date.now(),\n    });\n\n    return null;\n  },\n});\n\nexport const storeExternalData = internalMutation({\n  args: {\n    data: v.any(),\n    syncedAt: v.number(),\n  },\n  returns: v.null(),\n  handler: async (ctx, args) => {\n    await ctx.db.insert(\"externalData\", {\n      data: args.data,\n      syncedAt: args.syncedAt,\n    });\n    return null;\n  },\n});\n```\n\n```typescript\n// convex/crons.ts\nimport { cronJobs } from \"convex/server\";\nimport { internal } from \"./_generated/api\";\n\nconst crons = cronJobs();\n\ncrons.interval(\n  \"sync external data\",\n  { minutes: 15 },\n  internal.sync.syncExternalData,\n  {}\n);\n\nexport default crons;\n```\n\n## Examples\n\n### Schema for Cron Job Logging\n\n```typescript\n// convex/schema.ts\nimport { defineSchema, defineTable } from \"convex/server\";\nimport { v } from \"convex/values\";\n\nexport default defineSchema({\n  cronLogs: defineTable({\n    jobName: v.string(),\n    startTime: v.number(),\n    endTime: v.number(),\n    duration: v.number(),\n    processedCount: v.number(),\n    errorCount: v.number(),\n    status: v.union(\n      v.literal(\"success\"),\n      v.literal(\"partial\"),\n      v.literal(\"failed\")\n    ),\n    error: v.optional(v.string()),\n  })\n    .index(\"by_job\", [\"jobName\"])\n    .index(\"by_status\", [\"status\"])\n    .index(\"by_startTime\", [\"startTime\"]),\n\n  sessions: defineTable({\n    userId: v.id(\"users\"),\n    token: v.string(),\n    lastActive: v.number(),\n    expiresAt: v.number(),\n  })\n    .index(\"by_user\", [\"userId\"])\n    .index(\"by_lastActive\", [\"lastActive\"])\n    .index(\"by_expiresAt\", [\"expiresAt\"]),\n\n  tasks: defineTable({\n    type: v.string(),\n    status: v.union(\n      v.literal(\"pending\"),\n      v.literal(\"processing\"),\n      v.literal(\"completed\"),\n      v.literal(\"failed\")\n    ),\n    data: v.any(),\n    createdAt: v.number(),\n    startedAt: v.optional(v.number()),\n    completedAt: v.optional(v.number()),\n  })\n    .index(\"by_status\", [\"status\"])\n    .index(\"by_type_and_status\", [\"type\", \"status\"]),\n});\n```\n\n### Complete Cron Configuration Example\n\n```typescript\n// convex/crons.ts\nimport { cronJobs } from \"convex/server\";\nimport { internal } from \"./_generated/api\";\n\nconst crons = cronJobs();\n\n// Cleanup jobs\ncrons.interval(\n  \"cleanup expired sessions\",\n  { hours: 1 },\n  internal.cleanup.expiredSessions,\n  {}\n);\n\ncrons.interval(\n  \"cleanup old logs\",\n  { hours: 24 },\n  internal.cleanup.oldLogs,\n  { maxAgeDays: 30 }\n);\n\n// Sync jobs\ncrons.interval(\n  \"sync user data\",\n  { minutes: 15 },\n  internal.sync.userData,\n  {}\n);\n\n// Report jobs\ncrons.cron(\n  \"daily analytics\",\n  \"0 1 * * *\",\n  internal.reports.dailyAnalytics,\n  {}\n);\n\ncrons.cron(\n  \"weekly summary\",\n  \"0 9 * * 1\",\n  internal.reports.weeklySummary,\n  {}\n);\n\n// Health checks\ncrons.interval(\n  \"service health check\",\n  { minutes: 5 },\n  internal.monitoring.healthCheck,\n  {}\n);\n\nexport default crons;\n```\n\n## Best Practices\n\n- Never run `npx convex deploy` unless explicitly instructed\n- Never run any git commands unless explicitly instructed\n- Only use `crons.interval` or `crons.cron` methods, not deprecated helpers\n- Always call internal functions from cron jobs for security\n- Import `internal` from `_generated/api` even for functions in the same file\n- Add logging and monitoring for production cron jobs\n- Use batching for operations that process large datasets\n- Handle errors gracefully to prevent job failures\n- Use meaningful job names for dashboard visibility\n- Consider timezone when using cron expressions (Convex uses UTC)\n\n## Common Pitfalls\n\n1. **Using public functions** - Cron jobs should call internal functions only\n2. **Long-running mutations** - Break large operations into batches\n3. **Missing error handling** - Unhandled errors will fail the entire job\n4. **Forgetting timezone** - All cron expressions use UTC\n5. **Using deprecated helpers** - Avoid `crons.hourly`, `crons.daily`, etc.\n6. **Not logging execution** - Makes debugging production issues difficult\n\n## References\n\n- Convex Documentation: https://docs.convex.dev/\n- Convex LLMs.txt: https://docs.convex.dev/llms.txt\n- Cron Jobs: https://docs.convex.dev/scheduling/cron-jobs\n- Scheduling Overview: https://docs.convex.dev/scheduling\n- Scheduled Functions: https://docs.convex.dev/scheduling/scheduled-functions","author":"@waynesutton","ownerProfile":null,"authorContacts":null,"sourceUrl":"https://github.com/waynesutton/convexskills/tree/main/skills/convex-cron-jobs","license":"Apache-2.0","category":"document","lang":"en","tokens":3391,"stars":0,"calls30d":2,"claimed":false,"visibility":"public","origin":"crawler","version":"0.1.0","createdAt":"2026-08-22","updatedAt":"2026-08-22","files":[{"path":"agents/openai.yaml","size":91,"sha256":"bb57e6929f0916464111ae4a5e0a2ec5d301653b5a3b818a81dc2c0a7deb21c2"}],"requires":{"mcp":[],"tools":[]},"safety":{"flags":[],"scannedAt":"2026-08-22","hasScripts":false,"networkEndpoints":["api.example.com","docs.convex.dev"]}}