dw batch:cancel, dw batch:signal and dw batch:update act on every workflow a visibility
query selects, and survive interruption. Before anything is sent, the
selection is written to a manifest: a private file on your machine.
Each target's outcome is added to the manifest as it happens.
dw batch:show reads the manifest, and dw batch:resume continues it
without relisting and without resending anything that may already have
taken effect.
These commands are new in the Rust CLI. The 2.x workflow:cancel --all-matching still works as before; it keeps its results only in memory.
dw batch:cancel --query='customer-42' --reason=offboarded --manifest=cancel-42.jsonl
dw batch:signal approve --query='queue-7' -i '["ops"]' --manifest=approve-7.jsonl
dw batch:update set-limit --query='tier-2' -i '[5]' --wait=completed --manifest=limits.jsonl--queryis a visibility query;--typeand--status(defaultrunning) narrow it. At most--limitworkflows are selected (default 100, at most 1000).--manifestmust name a new file.dwcreates it readable only by you (mode 0600) and refuses a path that already exists.dwasks before sending, unless you pass--yes. With--jsonor--output=json,--yesis required.- Targets are sent one at a time, at most
--raterequests per second (default 10). --deadlinebounds the run: whole seconds, or a duration such as90s,5mor1h30m(at most 7 days). See Deadlines.batch:updatesends each update with--wait(accepted, the default, orcompleted), checked against the Server's request contract before anything is selected.
The manifest holds the Server URL, namespace, operation, its parameters (the cancellation reason, or the signal name and its input exactly as sent) and every selected target (for an update, also its wait policy). It never holds your token. Keep it as long as you need the record: it can contain workflow IDs and signal input.
Each workflow is pinned to the run its listing reported, and only that run is cancelled or signalled. If the workflow continues as a new run in the meantime, the successor is not touched. Visibility pages are not a transactional snapshot: the manifest records what was seen while paging, and resume never lists again.
Each target has its own command key, batch-<manifest-id>-<index>. Signals
and updates send it as request_id, and resume sends the same key again. Cancellation
has no command-key field on the Server; the key is recorded so that every
target can be correlated.
| State | Meaning | Resume |
|---|---|---|
acknowledged |
The Server accepted the command. For an update: accepted or completed. | Not sent again. |
skipped |
Nothing to do: the run is closed or not found (404, 409, 410), or the listing gave no run ID. | Not sent again. |
refused |
A permanent refusal, such as invalid input or a request-ID conflict, or an update the workflow rejected or failed. | Not sent again. |
pressure |
The Server asked the client to slow down (429, or 503 with Retry-After). |
Sent again with the same key, up to 3 attempts per target in total. |
unknown |
Sent, but dw does not know whether the Server admitted it: the response was lost or timed out, the Server failed (5xx), or dw stopped while waiting. |
Never sent again automatically. |
pending |
Not sent yet, or refused before admission (401/403). | Sent. |
During a run, dw honours Retry-After (up to 60 seconds) and retries a
pressure target in place. It stops the batch at the first unknown
outcome, at an authentication refusal, or when a target runs out of attempts
under pressure, or when its deadline passes. The remaining targets stay
pending for a later resume, and the reason is recorded in the manifest.
--deadline on batch:cancel, batch:signal, batch:update and
batch:resume bounds one run:
- No new request starts once the deadline has passed, or with less than half
a second left. Unsent targets stay
pending. - A
pressureretry whoseRetry-Afterwould end after the deadline is not waited for: the target stayspressureand is retried on resume. - A request already sent keeps its own timeout (60 seconds), shortened so it
never runs past the deadline. If that shortened timeout expires after the
request left, the target is
unknownand is never resent.
Resume the rest with dw batch:resume, with a new --deadline if you want
one.
The exit code is 0 when every target ended acknowledged or skipped, and 1
otherwise.
dw batch:show cancel-42.jsonl
dw batch:show cancel-42.jsonl --jsonbatch:show reads only the file. It makes no network requests, so you can
run it anywhere the manifest is.
dw batch:resume cancel-42.jsonl
dw batch:resume cancel-42.jsonl --yes --jsonResume uses your normal connection settings. It refuses to run unless they
name the same Server and namespace the manifest recorded, and every
request is authenticated again. It sends only pending targets and
pressure targets with attempts left, in their original order, with their
original keys and parameters. Only one dw process can run a manifest at a
time.
An unknown target may or may not have taken effect. Before you do anything
about it:
- Look at the workflow:
dw workflow:describe <workflow-id> --run-id=<run-id>. A cancelled run shows a closed status. - For a signal or update, check the history for it and its command receipt:
dw workflow:history <workflow-id> <run-id>, and look for the target'srequest_id(batch-<manifest-id>-<index>). - If it did not take effect and you still want it, send it once by hand,
for example
dw workflow:signal <workflow-id> <signal> --run-id=<run-id> --request-id=<the same key>, or the matchingdw workflow:update.
A request ID alone does not prove that repeating a signal is safe. Rely on
retained admission and intent-conflict guarantees only when the Server
advertises them; until it does, dw never resends an unknown target.
The file is JSON Lines with schema durable-workflow.cli.batch-manifest/v1.
- Line 1, the header:
schema,manifest_id,created_at,cli_version,server,namespace,operation(cancel,signalorupdate),parameters,selection(query, filters, limit and a note about snapshots) andtargets. Each target hasindex,workflow_id,run_idandcommand_key. - Every later line is an event.
{"event":"dispatching","index":n,"attempt":k,"at":…}is written before a request leaves.{"event":"outcome","index":n,"attempt":k,"state":…,"http_status":…,"reason":…,"message":…,"at":…}is written after it returns. {"event":"stopped","reason":…,"message":…,"at":…}names no target. It records why a run stopped early:deadline,unknown,pressureorauthentication.batch:showreports the latest one, and--jsonoutput lists them all understops.
Each line is synced to disk before dw continues. A target whose last event
is dispatching is unknown. A final line cut short by a crash is ignored
and removed on the next resume.