feat(server): add Routines/Cron primitive and scheduler (#2284)#2315
feat(server): add Routines/Cron primitive and scheduler (#2284)#2315ashishSoni1234 wants to merge 5 commits into
Conversation
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
@ashishSoni1234 is attempting to deploy a commit to the Different AI Team on Vercel. A member of the Team first needs to authorize it. |
|
Hi @dhruv-db, I have built the core backend primitive for Crons/Routines to solve your request! The @obchain @Abhijeet1005 - The backend foundation and API surface for the Routines feature is ready for review. I've mirrored the Please take a look when you get a chance! Also, let me know if we need vercel deployment authorization to run the CI checks. |
There was a problem hiding this comment.
4 issues found across 9 files
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
1 issue found across 3 files (changes from recent commits).
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
|
Hey @ashishSoni1234, thanks for putting this together! The routines scheduler is a great addition. I did a quick local review and run-through on Windows, and found a few critical runtime bugs and quality fixes that we need to address before this can be merged:
|
|
Hi @devcool20, thanks for the thorough review and great catches! I've pushed a new commit that addresses all your points and the AI reviewer's feedback:
Let me know if this looks good to go! |
There was a problem hiding this comment.
1 issue found across 4 files (changes from recent commits).
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
There was a problem hiding this comment.
1 issue found across 1 file (changes from recent commits).
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="apps/server/src/scheduler.ts">
<violation number="1" location="apps/server/src/scheduler.ts:93">
P2: Sequential promise queue for routine execution can grow unboundedly if triggerRoutine hangs or is very slow, causing memory leaks and stale accumulated executions. The previous `runningRoutines` Set skipped overlapping triggers to bound concurrency; the new `routineQueues` Map chains every trigger indefinitely without a queue depth limit or execution timeout.</violation>
</file>
Tip: Review your code locally with the cubic CLI to iterate faster.
Re-trigger cubic
| } | ||
| } | ||
|
|
||
| private executeRoutineInBackground(workspace: WorkspaceInfo, routine: RoutineItem) { |
There was a problem hiding this comment.
P2: Sequential promise queue for routine execution can grow unboundedly if triggerRoutine hangs or is very slow, causing memory leaks and stale accumulated executions. The previous runningRoutines Set skipped overlapping triggers to bound concurrency; the new routineQueues Map chains every trigger indefinitely without a queue depth limit or execution timeout.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/server/src/scheduler.ts, line 93:
<comment>Sequential promise queue for routine execution can grow unboundedly if triggerRoutine hangs or is very slow, causing memory leaks and stale accumulated executions. The previous `runningRoutines` Set skipped overlapping triggers to bound concurrency; the new `routineQueues` Map chains every trigger indefinitely without a queue depth limit or execution timeout.</comment>
<file context>
@@ -90,18 +90,29 @@ export class CronScheduler {
}
- private async executeRoutineInBackground(workspace: WorkspaceInfo, routine: RoutineItem) {
+ private executeRoutineInBackground(workspace: WorkspaceInfo, routine: RoutineItem) {
const key = `${workspace.id}:${routine.name}`;
- if (this.runningRoutines.has(key)) {
</file context>
| private executeRoutineInBackground(workspace: WorkspaceInfo, routine: RoutineItem) { | |
| private executeRoutineInBackground(workspace: WorkspaceInfo, routine: RoutineItem) { | |
| const key = `${workspace.id}:${routine.name}`; | |
| if (this.routineQueues.has(key)) { | |
| console.warn(`Skipping overlapping routine trigger for ${routine.name}`); | |
| return Promise.resolve(); | |
| } | |
| const promise = this.triggerRoutine(workspace, routine) | |
| .catch((err) => { | |
| console.error(`Error in background routine ${routine.name}:`, err); | |
| }) | |
| .finally(() => { | |
| this.routineQueues.delete(key); | |
| }); | |
| this.routineQueues.set(key, promise); | |
| return promise; | |
| } |
Pablosinyores
left a comment
There was a problem hiding this comment.
scheduler.ts imports createWorkspaceOpencodeClient from ./server.js, but the class only ever uses the injected this.createClient callback — the direct import is unused. Since server.ts imports scheduler.ts, that unused import sets up a server <-> scheduler circular dependency for no reason (the inline comment // We need to export this or pass a callback looks like a leftover from before the switch to callback injection). Dropping the import removes the cycle, and the callback pattern is the right call.
A few smaller things:
CronExpressionParser.parse(routine.schedule, { currentDate: ... })runs in the servers local timezone with no per-routinetz. For a user-facing "every hour" / "every day at 9" feature this fires in server time, which can surprise users in other zones — worth either documenting it or threading a timezone throughRoutineItem.triggerRoutinealready wraps its body in try/catch and logs, andexecuteRoutineInBackgroundwraps the same call again with another.catch. The inner handling makes the outer one redundant.- The SDK return-shape sniffing (
"data" in result) suggests the client is typedany. IfcreateWorkspaceOpencodeClientexported its return type,session.creates result could be typed and the runtime shape check dropped.
Summary
CronSchedulerservice to automatically trigger open-ended tasks (like Claude co-work) on a recurring schedule.Why
Issue
Scope
RoutineItemprimitive intypes.ts.routines.tsandserver.tsAPI endpoints (storing in.opencode/routines/<name>.md).CronScheduler(scheduler.ts) usingcron-parserto evaluate schedules every minute.opencode.session.create()) upon cron trigger.Out of scope
Testing
Ran
pnpm run typecheckacross all server packagesResult
CI status
Manual verification
.mdfrontmatter.cron-parserevaluates valid schedules and handles malformed strings without crashing the background thread.Evidence
Risk
CronSchedulerisolates exceptions per routine. If a schedule fails to parse, it skips it and logs an error, preventing the server from crashing. Time validation acts upon isolated boundaries.Rollback
.mdfile deletion if needed.