Think scheduled tasks: "every 1 minutes" throws at reconcile, after app data is stored, and one invalid task stops all tasks reconciling · Issue #2638 · cloudflare/agents · GitHub
You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Think scheduled tasks: "every 1 minutes" throws at reconcile, after app data is stored, and one invalid task stops all tasks reconciling #2638
Versions:@cloudflare/think 0.20.2. Still reproduces on 0.21.0, and the inspected main at fc7f59f has the same code.
What happens
getScheduledTasks() may read app-owned data. The docs say to call internal_reconcileScheduledTasks() after changing it. Three problems follow:
Strict plurals. The interval grammar accepts minute|minutes|hour|hours, then throws Use singular unit for "every 1 minutes" or Use plural unit for "every 2 minute". The meaning is unambiguous either way. A model or a user writing a schedule gets this wrong often, and the ThinkIntervalSchedule template type allows both forms.
Checked only at reconcile. Nothing public validates a schedule before the app stores it. _declaredScheduleValidationError exists but is private. The error surfaces after the row is written.
All or nothing._normalizeDeclaredScheduledTasks() throws on the first invalid task, so no task is armed or updated, including valid ones. On startup the same throw is caught by _runBestEffortOnStartStep, which logs "Declared scheduled tasks were not reconciled on this wake; the next successful wake will reconcile them". But no wake will succeed until the bad row is gone.
Reproduction
exportclassScheduleAgentextendsThink<Env>{getDefaultTimezone(){return"UTC";}getScheduledTasks(){consttasks: ThinkScheduledTasks={};for(constjofthis.sql`SELECT id, schedule FROM jobs`)tasks[j.id]={schedule: j.schedule,handler: async()=>{}};returntasks;}asyncset(schedules: string[]){/* replace the jobs rows */awaitthis.internal_reconcileScheduledTasks();// throws for the cases below}}
schedules
reconcile
tasks armed
["every 2 minutes"]
ok
1
["every 1 minutes"]
Use singular unit for "every 1 minutes"
0
["every 2 minute"]
Use plural unit for "every 2 minute"
0
["every 2 minutes", "every 1 minutes"]
Use singular unit …
0
Expected: accept either plural form and normalise it. Skip an invalid task with a warning, or report it per task, and arm the rest. Offer a public validator so apps can reject bad wording before storing it.
Proposed fix
In parseDeclaredTaskScheduleUnchecked, drop the two plural checks and normalise to every N minute(s).
In _normalizeDeclaredScheduledTasks, use tryParseDeclaredTaskSchedule, then console.warn and continue on error.
Export validateScheduledTaskSchedule(schedule, { timezone?, defaultTimezone? }): string | null, the public form of _declaredScheduleValidationError.
With the first two applied to the 0.21.0 build, all four cases above arm every valid task. ["every 2 minutes", "every 1 minutes", "every day at 9:00"] arms 2 tasks and logs Scheduled task "job2" skipped: Unsupported schedule DSL "every day at 9:00".
Activity