Skip to main content
Version: v5.6.1

5.6 Maintenance windows

maintenance

After this you can: open a maintenance window on a node subtree, see what quiesces and what defers, override with an audit trail, and release the deferred work in order.

Before you start — tick each one:

  • Lesson 5.2 — Triggers (opschain learn 5.2)

In the GUI: the Maintenance panel in the Operations area — schedule, start, extend, end, cancel, and each window's deferred firings — plus the maintenance badge on covered nodes.

Once triggers run your estate, you need a way to say not right now — without disabling the automation you spent lessons 5.1 to 5.4 building. A maintenance window quiesces exactly one node subtree: new work is refused with a named error, trigger firings park instead of firing, the window's own declared work runs stamped — and when the window ends, the parked work is released in order. Nothing is lost, everything is audited. You need at least one trigger (lesson 5.2) to see deferral happen, and a licence with the maintenance feature — the Basic tier does not include it (opschain docs entitlements).

1. Start a window​

opschain maintenance start acme/environments/dev \
--reason "db engine upgrade" --until 2026-10-13T02:00:00Z \
--allow-workflow patch_release --allow-action db:upgrade --allow-user alice
started maintenance on acme/environments/dev (window 01a14900-…-example)
  • --reason is required — it appears in every refusal, badge and audit record the window produces.
  • --until is declared intent only on a manual window: it tells operators (and deferral expiry, section 5) when you meant to finish, but the window never auto-closes — you end it. Scheduled windows (section 6) are the hands-off alternative.
  • The --allow-* lists declare the window's own maintenance work: workflow codes, change actions and usernames admitted through the gate.

Check it, from the node's point of view and the estate's:

opschain maintenance status acme/environments/dev
node: acme/environments/dev
window: 01a14900-…-example
state: active
active: true
reason: db engine upgrade
until: 2026-10-13T02:00:00Z
started_by: alice
started_at: 2026-10-12T22:04:11Z
allow workflows: patch_release
allow actions: db:upgrade
allow users: alice
deferred firings: 0
opschain maintenance list # every active and scheduled window, estate-wide
opschain nodes list acme # the MAINTENANCE badge from lesson 1.1

Starting emits a triggerable api:nodes:maintenance_started event on the node path — "when maintenance starts, notify the channel" is an ordinary lesson 5.2 trigger. Only one window — active or scheduled — can exist per node at a time.

2. What quiesces — and what doesn't​

The gate is strict in scope and runs at creation time:

  • A non-allowlisted create targeting the subtree answers a named 422:
opschain runs create change acme/environments/dev/assets/web --action web:deploy
Error: the server answered HTTP 422: /projects/acme/environments/dev is under maintenance: db engine upgrade

Retries of old failures cross the same gate.

  • The same create on a sibling node proceeds normally — the window covers its subtree, nothing else.
  • In-flight work is untouched. A change already running when the window starts finishes on its own terms; maintenance mode gates new work, it never kills existing work.
  • Allowlisted work runs, stamped: the change or run carries maintenance_stamp: allowed, visible in runs show -o json and the audit record. An allowed workflow's nested spawns inherit the allowance — one authorised operation, not a per-child lottery.

3. Automation defers, never loses​

A trigger firing whose action targets the quiesced subtree settles deferred: the exactly-once record is claimed, nothing is created, and the reason names the window. Trigger lineage is never allowlisted — events emitted by the maintenance work itself still defer any trigger they match; automation does not ride in on the humans' allowance.

opschain maintenance status acme/environments/dev # deferred firings: N
opschain triggers firings acme/promote_on_success --deferred
WHEN STATUS CREATED ERROR
2026-10-12T22:10:40.118204Z deferred - deferred: /projects/acme/environments/dev is under maintenance: db engine upgrade

The deferral rides the matched event's chain as an info:triggers:deferred hop too, so chains show displays exactly where automation paused (lesson 5.1), and chains triage lists it as a caveat.

4. The escape hatches — both audited​

Rights first: the window rides the node's …/maintenance rule path — update on it starts and ends windows, overrides the gate and discards parked firings.

  • Per-request override. A create carrying override_maintenance: true (in the -f payload.yaml of runs create change or runs create workflow) is honoured for holders of that update right: the run proceeds stamped overridden, and an audit:maintenance:override event records who pushed what through which window.
  • Selective discard. Prune a parked firing before release — stale work you know should not fire after the window:
opschain triggers firings acme/promote_on_success --deferred --wide # find the firing id
opschain triggers discard acme/promote_on_success --firing 01a14905-…-example
discarded firing 01a14905-…-example of trigger acme/promote_on_success — settled suppressed, it will not release

The firing settles suppressed with the discarding user named in its error; the window's release skips it. Only a deferred firing can be discarded — anything else is a named 422.

5. End and release​

opschain maintenance end acme/environments/dev
ended maintenance on acme/environments/dev (window 01a14900-…-example)

Ending emits the triggerable api:nodes:maintenance_ended and releases the still-deferred firings in original event order, re-evaluating each against the world as it now is — conditions, throttle, depth cap, authorisation, template resolution all run again. A trigger disabled mid-window suppresses visibly; a target now covered by another window re-defers into it. And a deferral parked past the window's declared --until expires instead of firing into an unwatched estate: it settles suppressed with the expiry reason, and a warn:triggers:deferral_expired event lands on the window's node — caught by your events --errors sweep.

opschain triggers firings acme/promote_on_success --since 1h
WHEN STATUS CREATED ERROR
2026-10-12T23:30:02.551190Z created WorkflowRun/01a14920-7a1c -

The parked firing is now created, with the run it made.

6. Scheduled windows: the hands-off lifecycle​

A scheduled window starts and ends by itself, carries its own work, and keeps itself open exactly as long as that work needs:

opschain maintenance schedule acme/environments/dev \
--from 2026-10-13T01:00:00Z --to 2026-10-13T02:00:00Z \
--reason "nightly patch" \
--run patch_release --auto-extend 15m --max-extension 2h --auto-end
scheduled maintenance on acme/environments/dev from 2026-10-13T01:00:00Z to 2026-10-13T02:00:00Z (window 01a14930-…-example)
  • At --from the window activates: gating, deferral and badges engage exactly as after a manual start, and each --run workflow is started as you — human lineage, stamped allowed, counted as the window's own work.
  • --to is a hard end the server enforces (unlike a manual window's advisory --until).
  • --auto-extend 15m keeps pushing the end out while the window's stamped work is still running — each extension an api:nodes:maintenance_extended event — up to --max-extension 2h. The cap is fail-open: the window ends anyway with a loud warn:maintenance:auto_extend_cap; ending never kills in-flight work, it only resumes deferred automation.
  • --auto-end closes the window as soon as all its work has finished, whatever the outcome — no more waiting for --to when the patch run finished in twenty minutes.

Manual control still works mid-flight:

opschain maintenance extend acme/environments/dev --by 30m # or --to <rfc3339>; never shortens
opschain maintenance end acme/environments/dev # finish early, release now
opschain maintenance cancel acme/environments/dev --window 01a14930-…-example # a scheduled window not yet started
extended maintenance on acme/environments/dev until 2026-10-13T02:30:00Z (window 01a14930-…-example)
cancelled scheduled maintenance on acme/environments/dev (window 01a14930-…-example)

cancel removes a pending window only — an active one can only be ended. The full lifecycle lands on the node path as events — maintenance_scheduled → started → extended… → ended — readable with opschain events --reverse.

7. The estate view​

opschain maintenance list # active + scheduled, estate-wide
opschain maintenance list --ended --since 24h # plus recent history
opschain maintenance list --wide # untruncated allow lists
NODE STATE REASON STARTED BY … ALLOW DEFERRED
/projects/acme/environments/dev active db engine upgrade alice … patch_release, db:upgrade, alice 2

This is the "what is quiesced right now" dashboard — pair it with the MAINTENANCE badge in nodes list when you are looking at one subtree.

A maintenance window is the subtree-sized rung of a wider ladder: a runner or site can be drained and quiesced, and the whole platform can be halted — each rung with the same "nothing lost, everything audited" contract. Lesson 8.4 (opschain learn 8.4) walks that posture ladder; windows are the rung you reach for when the estate, not the deployment, needs the pause.

Check yourself​

  • Start a window, let your trigger match an event inside it, then end the window: what status does the firing carry before and after, and in which order are several parked firings released?

What you learned​

  • A window quiesces exactly one subtree at creation time: named 422s for non-allowlisted creates, in-flight work untouched, allowlisted work stamped allowed.
  • Automation defers, never loses: firings park as deferred, visible in triggers firings --deferred, on the chain, and in maintenance status.
  • The escape hatches are audited: override_maintenance runs stamped overridden with an audit:maintenance:override event; triggers discard suppresses a parked firing with the discarder named.
  • Ending releases in original order, re-evaluated; deferrals past the declared end expire loudly instead of firing blind.
  • maintenance schedule --from --to --run … --auto-extend --max-extension --auto-end is the hands-off lifecycle; extend/end/cancel keep manual control; maintenance list is the estate view; lesson 8.4 is the wider ladder.

Next: lesson 5.7 — Lab: the hand-off pattern (opschain learn 5.7), or back to your track (opschain learn).