One-shot hooks
Hooks always run in batches. Each script process sees batch env vars:
| Variable | Single-batch run |
|---|---|
BATCH_INDEX |
0 |
BATCH_COUNT |
1 |
BATCH_ALLOCATIONS |
Comma-separated IPs in that sole batch (one or many workers) |
A one-shot run means the hook completes in one batch — no second wave with BATCH_INDEX=1. Use this for operator maintenance, global migrations, or scripts that must see every allocation at once.
See also: hooks.md · hook-api.md
One worker
When the job has one active allocation, every cli / deploy event naturally produces BATCH_COUNT=1 and BATCH_INDEX=0.
maand hooks hook_status api --verbose
To target one worker while others stay active, register the hook for job_control (not only cli) and pass --allocations:
maand job run api --target migrate --allocations 10.0.0.2
Maand runs the job_control script on that worker only. Env includes TARGET=migrate, BATCH_COUNT=1, and BATCH_ALLOCATIONS=10.0.0.2.
maand hooks has no worker filter — it always includes all active allocations for the job.
All workers in one batch
Set max_concurrent_upgrades in manifest.json to at least the number of active allocations, then rebuild:
{
"selectors": ["worker"],
"max_concurrent_upgrades": 8,
"hooks": {
"hook_cluster_status": {
"executed_on": ["cli"]
}
}
}
maand build
maand hooks hook_cluster_status api --verbose
Every active worker runs in parallel inside batch 0. Scripts see BATCH_COUNT=1 and BATCH_ALLOCATIONS listing all IPs (order follows rollout_order).
Tune max_concurrent_upgrades back down before production deploys if you normally roll in smaller bursts — see rolling-deploy.md.
Guard in the script
Maand starts one hook process per allocation in each batch. Every process in the same batch wave sees the same BATCH_* env, but each has its own ALLOCATION_INDEX. A check like BATCH_COUNT == 1 alone still runs on every allocation — not once per maand invocation.
For logic that must run exactly once (leader election, global setup, one-time debug logging), guard with is_one_shot() / isOneShot() from embedded maand.py / maand.ts (equivalent to BATCH_INDEX == "0" && ALLOCATION_INDEX == "0"):
import { isOneShot } from "./maand";
if (isOneShot()) {
console.log("BATCH_ALLOCATIONS=" + process.env.BATCH_ALLOCATIONS);
console.log("BATCH_COUNT=" + process.env.BATCH_COUNT);
console.log("BATCH_INDEX=" + process.env.BATCH_INDEX);
}
import maand
if maand.is_one_shot():
import os
print("BATCH_ALLOCATIONS=" + os.environ.get("BATCH_ALLOCATIONS", ""))
print("BATCH_COUNT=" + os.environ.get("BATCH_COUNT", ""))
print("BATCH_INDEX=" + os.environ.get("BATCH_INDEX", ""))
Env vars are always strings — compare to "0", not 0, when checking raw env.
Other helpers for per-allocation or per-wave logic:
def is_single_batch() -> bool:
return int(os.environ.get("BATCH_COUNT", "1")) == 1
def is_first_batch() -> bool:
return int(os.environ.get("BATCH_INDEX", "0")) == 0
# Per-allocation work on every worker when the invocation is one batch wave
if is_single_batch():
update_local_state()
# Per-allocation work only in the first batch wave of a rolling deploy
if is_first_batch():
prepare_canary()
| Check | Use when |
|---|---|
BATCH_COUNT == 1 |
Entire invocation is a single batch wave (all workers parallel) |
BATCH_INDEX == 0 |
Logic should run only in the first batch wave of a rolling deploy |
is_one_shot() / isOneShot() |
Logic must run exactly once (leader / debug print / global setup) |
ALLOCATION_IP in BATCH_ALLOCATIONS.split(",") |
Logic scoped to workers in the current batch |
Events compared
| How you invoke | Event | Worker scope | One batch (INDEX=0, COUNT=1) |
|---|---|---|---|
maand hooks … |
cli |
All active | One worker, or max_concurrent_upgrades ≥ active count |
maand job run … --allocations ip |
job_control |
Filtered actives | One IP in --allocations, or batch size ≥ filtered count |
maand deploy |
deploy hooks | Varies by event | Same rules; batch width from manifest |
Deploy pre_deploy / post_deploy / post_build follow the same batch rules as cli. Override order for the run with put_rollout_order() in pre_deploy if needed — hook-api.md.
Checklist
- Add
executed_on(clifor ad-hoc, orjob_controlfor per-workermaand job run). - Set
max_concurrent_upgrades(and rebuild) when all workers must shareBATCH_COUNT=1. - Test:
maand hooks hook_<name> <job> --verboseand confirm env in logs or script output. - Restore manifest batch size before rolling production deploys.