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)
--reasonis required — it appears in every refusal, badge and audit record the window produces.--untilis 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 inruns show -o jsonand 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.yamlofruns create changeorruns create workflow) is honoured for holders of that update right: the run proceeds stampedoverridden, and anaudit:maintenance:overrideevent 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
--fromthe window activates: gating, deferral and badges engage exactly as after a manual start, and each--runworkflow is started as you — human lineage, stampedallowed, counted as the window's own work. --tois a hard end the server enforces (unlike a manual window's advisory--until).--auto-extend 15mkeeps pushing the end out while the window's stamped work is still running — each extension anapi:nodes:maintenance_extendedevent — up to--max-extension 2h. The cap is fail-open: the window ends anyway with a loudwarn:maintenance:auto_extend_cap; ending never kills in-flight work, it only resumes deferred automation.--auto-endcloses the window as soon as all its work has finished, whatever the outcome — no more waiting for--towhen 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 intriggers firings --deferred, on the chain, and inmaintenance status. - The escape hatches are audited:
override_maintenanceruns stampedoverriddenwith anaudit:maintenance:overrideevent;triggers discardsuppresses 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-endis the hands-off lifecycle;extend/end/cancelkeep manual control;maintenance listis 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).