
# Scheduled wake

Recurring wake windows for a server in BLACKHOLE mode: the platform starts bringing the server up ahead of the moment set by a cron expression, with a margin for boot, and the task itself is fired by the app's own cron once the machine is up — the platform never reads or runs the app's code.

The server is not guaranteed to be up by the exact moment: a boot can take several minutes longer. Write a time-critical task so that, once the server starts, it checks whether its run time has already passed and, if so, runs late — or keep the server in the “Always online (24/7)” mode. The `.com` plans are non-preemptible; the advisory fields in the create and update responses are therefore `null` here — see [Create a window](./wake-schedules/create.md).

**A deploy changes neither the windows nor the sleep timer.** The wake schedule and the auto-sleep timer are stored on the platform, not in the application code, so shipping a new version does not reset them — the windows keep working with no extra action. The only thing the deploy takes from the schedule is the windows' timezone: on a dedicated virtual machine it lands in the `TZ` environment variable when every enabled window declares the same zone. Details — [Full application deploy](/docs/infra/deploy/deploy).

**Creating or updating a wake schedule normally requires a numeric auto-sleep setting.** Creating or updating any window while `sleepAfterMinutes` is `null` (“Never”) is rejected as an always-on conflict, even when that window is disabled. In the reverse direction, setting `null` is rejected only while an enabled window exists and the server is not in the `SCHEDULE` run mode; existing disabled windows neither affect the timeout nor block that sleep-setting change. An explicit numeric `sleepAfterMinutes` remains the effective timeout and is not shortened by the schedule. Existing older records are not rewritten: if one already contains `null` plus an enabled and entitled schedule with a computed next wake, the platform's post-window timeout applies — 15 minutes of inactivity by default. When there are no effective windows, `null` once again means no idle auto-sleep. Creating or updating a window with `null` is not rejected for an app in a galaxy: the app does not choose its plan but inherits it from the galaxy. Nor is it rejected for a server in the `SCHEDULE` [run mode](/docs/infra/lifecycle/run-mode): its sleep is declared by the schedule.

Scope: `vibe:infra`

## Operations

- [Create a window](./wake-schedules/create.md) — `POST /v1/infra/servers/:id/wake-schedules`
- [List windows](./wake-schedules/list.md) — `GET /v1/infra/servers/:id/wake-schedules`
- [Update a window](./wake-schedules/update.md) — `PATCH /v1/infra/servers/:id/wake-schedules/:scheduleId`
- [Delete a window](./wake-schedules/delete.md) — `DELETE /v1/infra/servers/:id/wake-schedules/:scheduleId`

## Scenarios

**A weekday morning report:**

1. [`POST /wake-schedules`](./wake-schedules/create.md) with `cronExpr: "0 9 * * 1-5"`, `timezone: "America/New_York"`.
2. The response carries `tzWarningCode: "SINGLE_ZONE"` — the timezone is unambiguous, and the platform injects it into the `TZ` environment variable on the next deploy of a dedicated virtual machine.
3. The cron inside the server fires the task at 9:00 New York time. The platform starts bringing the server up ahead of time, with a margin for boot, but no plan guarantees the server is up by that moment: a boot can take several minutes longer. If a task must not miss a run, write it so that, once the server starts, it checks whether its run time has already passed and, if so, runs late.

**Several windows across different timezones:**

1. Create windows with different `timezone` values for the same server.
2. Each mutation response carries `tzWarningCode: "MULTI_ZONE"` — the platform does not auto-inject `TZ` when windows span more than one zone. Handle time inside your app in UTC.

**A Galaxy app — a wider wake margin:**

1. For a server with `kind: "GALAXY_APP"`, the platform already budgets a wider margin for bringing up the host, the tunnel, and the container. Set your own `lead` only if the app needs even more time to boot: a value below the platform margin is raised to it.
2. The Galaxy host itself (`kind: "GALAXY"`) does not accept windows — the schedule is declared on a specific app, and the platform brings up the host too when needed.

## See also

- [Infrastructure](/docs/infra)
- [What the app receives](/docs/infra/app-runtime)
- [Lifecycle](/docs/infra/lifecycle)
- [Changelog](/docs/changelog)
