Skip to content

feat(eve): external cron mode for self-hosted builds (EVE_EXTERNAL_CRON=1)#710

Open
warmwind wants to merge 2 commits into
vercel:mainfrom
warmwind:feat-external-cron
Open

feat(eve): external cron mode for self-hosted builds (EVE_EXTERNAL_CRON=1)#710
warmwind wants to merge 2 commits into
vercel:mainfrom
warmwind:feat-external-cron

Conversation

@warmwind

@warmwind warmwind commented Jul 11, 2026

Copy link
Copy Markdown

Closes #707

Problem

Self-hosted (node preset) builds fire schedules from Nitro's in-process runner with no off switch: N replicas fire every schedule N times, ticks that land while the process is down are silently lost, and production exposes no dispatch endpoint — so a hosting platform can neither pause a schedule nor trigger one manually, and the container must stay always-on just to keep the clock. None of this applies on Vercel, where the platform owns the clock through the unguessable token cron route.

Change

Building with EVE_EXTERNAL_CRON=1 (node preset, production only — no effect on Vercel builds or eve dev):

  • No in-process cron. registerScheduleTaskHandlers gains registerCronSchedules: false: task handlers are registered as before, but nothing is written to scheduledTasks, so startScheduleRunner in the node-server entry no-ops.
  • Token cron route on the node preset. The build mounts POST /eve/v1/cron/<token> (reusing createEveCronHandlerRoute). The handler mirrors Nitro's Vercel cron handler contract — the due cron expression arrives in x-vercel-cron-schedule, and a configured CRON_SECRET is enforced as a bearer token with a timing-safe compare — and dispatches every schedule registered for that expression through dispatchScheduleTask (registrations are baked into the virtual handler, since scheduledTasks is deliberately empty in this mode).
  • Cron manifest in build output. compiled writes .output/eve/cron-manifest.json with the route path and one entry per distinct cron expression, each listing the schedules it dispatches — the self-hosted equivalent of the config.crons[] Vercel reads from build output. The path is the credential, so the manifest lives only in build output (readable by whoever ran the build) and is never served.

An external scheduler then drives the deployment exactly like Vercel Cron does: on each due tick, POST the route with the cron expression in the header. Keeping the request contract identical means one scheduler client works against both Vercel and self-hosted deployments.

Testing

  • Unit: registerCronSchedules: false registers tasks/virtual modules with scheduledTasks left empty; route registration bakes in the schedule map at an unguessable path; isExternalCronEnabled parsing; handler tests for dispatch-by-expression, empty match, missing header (400), and CRON_SECRET enforcement (401/200).
  • Integration: the compiled hook writes the expected manifest JSON into the output directory.
  • pnpm test:unit (4439), pnpm test:integration (386), package typecheck, lint, guard:invariants, docs:check all pass.

🤖 Generated with Claude Code

https://claude.ai/code/session_0164WUaSv9mN58wgnnoN2vKG

Self-hosted (node preset) builds fire schedules from Nitro's in-process
runner with no off switch: replicas duplicate every tick, downtime
loses ticks silently, and production exposes no dispatch endpoint, so
a hosting platform can neither pause a schedule nor trigger one.

Building with EVE_EXTERNAL_CRON=1 now registers the schedule task
handlers without any scheduledTasks entries — the in-process runner
never starts — and instead mounts the same unguessable token cron
route the Vercel preset uses. The route mirrors Nitro's Vercel cron
handler contract (x-vercel-cron-schedule header, optional CRON_SECRET
bearer check) and dispatches through dispatchScheduleTask. The build
writes .output/eve/cron-manifest.json (route path plus each schedule's
name and cron) — the self-hosted equivalent of the config.crons[]
Vercel reads from build output. No effect on Vercel builds or eve dev.

Closes vercel#707

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0164WUaSv9mN58wgnnoN2vKG
Signed-off-by: Oscar Jiang <pengj0520@gmail.com>
@vercel

vercel Bot commented Jul 11, 2026

Copy link
Copy Markdown
Contributor

@warmwind is attempting to deploy a commit to the Vercel Team on Vercel.

A member of the Team first needs to authorize it.

Comment thread packages/eve/src/internal/nitro/host/external-cron.ts
The dispatch route fires every schedule registered for the posted
expression, so a manifest entry per schedule invites schedulers to
create one job per schedule and double-dispatch shared expressions.
Mirror the Vercel preset, which keys config.crons on scheduledTasks:
one entry per distinct expression, listing the schedules it dispatches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0164WUaSv9mN58wgnnoN2vKG
Signed-off-by: Oscar Jiang <pengj0520@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Self-hosted: external cron mode — disable the in-process scheduler and expose the token cron route on the node preset

1 participant