Dependencies & trigger rules¶
In this chapter you'll wire tasks into a real dependency graph with deps, see what happens downstream when a task fails, and take control of that behavior with trigger rules — plus the two operator tools for recovering a bad run: cronova retry and cronova mark.
Wire tasks together with deps¶
A cronova DAG is a directed acyclic graph: tasks are the nodes, and each task's deps list draws the incoming edges. A task runs only when its upstream tasks (the ones it names in deps) have reached the states its trigger rule demands — by default, when they have all succeeded.
Create dags/daily_etl.yaml, a classic extract → transform → load chain. Leave schedule out so it only runs when you trigger it:
dag_id: daily_etl
tasks:
- id: extract
type: shell
command: echo "extracting rows"
- id: transform
type: shell
command: echo "transforming"
deps: [extract]
- id: load
type: shell
command: echo "loading"
deps: [transform]
deps is a list, so graphs don't have to be straight lines: a task with deps: [a, b] fans in and waits for both, and two tasks that both name deps: [extract] fan out and run in parallel.
Every DAG file is validated and cycle-checked on load. If you accidentally wire a loop (say, extract depending on load), the file is rejected with a dependency cycle detected error in the serve log and skipped — a cycle can never silently deadlock the workflow scheduler.
Check it — trigger the DAG and watch the chain execute in order:
RUN_ID LOGICAL_DATE STATE TRIGGER TASKS
daily_etl__manual_1783472901234567000 2026-07-07T09:08:21Z success manual extract=success transform=success load=success
In the console at http://localhost:8090, open daily_etl and you'll see the same graph drawn as nodes and edges — the visual counterpart of your deps lists.
When an upstream fails: upstream_failed¶
What happens to load if transform fails? Try it. Change the transform command to fail on purpose:
Save, trigger again, and check:
RUN_ID LOGICAL_DATE STATE TRIGGER TASKS
daily_etl__manual_1783473010987654000 2026-07-07T09:10:10Z failed manual extract=success transform=failed load=upstream_failed
load never ran. When an upstream task fails, cronova marks the tasks below it upstream_failed — a terminal state meaning "blocked by an upstream failure, never executed". Propagation follows the edges: only the failed task's descendants are blocked, while unrelated parallel branches of the same run keep executing to completion. A task can even be caught by this while already queued, if its upstream fails before the executor picks it up.
Any failed or upstream_failed task makes the whole run finish as failed — which is what you see in the STATE column.
Trigger rules¶
The default gate — "run when all upstreams succeeded" — is one of six. Set a task's trigger_rule to change when it fires relative to its deps:
| Rule | Runs when |
|---|---|
all_success (default) |
every upstream task succeeded |
all_done |
every upstream task finished (any state) |
all_failed |
every upstream task failed |
one_success |
at least one upstream task succeeded |
one_failed |
at least one upstream task failed |
none_failed |
no upstream task failed (success or skipped) |
Two of these solve everyday problems. A cleanup task should run whether the pipeline worked or not — that's all_done. An alert task should run precisely because something failed — that's one_failed. Add both to daily_etl (keep transform failing for now):
- id: cleanup
type: shell
command: echo "removing temp files"
deps: [extract, transform, load]
trigger_rule: all_done
- id: alert
type: shell
command: echo "ALERT daily_etl failed" # curl your pager here
deps: [transform, load]
trigger_rule: one_failed
Check it — trigger once more:
RUN_ID LOGICAL_DATE STATE TRIGGER TASKS
daily_etl__manual_1783473120123456000 2026-07-07T09:12:00Z failed manual extract=success transform=failed load=upstream_failed cleanup=success alert=success
transform failed and load was blocked exactly as before — but cleanup ran anyway (all_done), and alert fired because a dependency failed (one_failed). Open the alert task's log in the console to see the message.
Warning
A task whose trigger rule can never be satisfied anymore is marked
upstream_failed — and any upstream_failed task makes the run's recorded
state failed. On a fully green run, an unconditional one_failed alert
task can never fire, so it ends upstream_failed and the successful
pipeline is recorded as a failed run. Use one_failed / all_failed
branches to react to failures inside a run you already expect to be red;
for plain "notify me when a run fails", prefer the DAG-level notify
webhook (see the DAG Reference) and remove the
alert task before putting this DAG on a schedule.
Retry a failed run: cronova retry¶
Now fix the bug — restore transform to a working command:
Saving the file doesn't rewrite history: the failed run stays failed. To re-run just the broken parts, use cronova retry with the run id from cronova runs:
Note
retry, mark, and cancel are operator verbs that talk to the running
server's REST API, so they need a target: pass
-server http://localhost:8090 (or export CRONOVA_SERVER). If you
enabled login with -auth, also supply an API token via -token /
CRONOVA_TOKEN — mint one with cronova tokens create. All the details
are in the CLI Reference.
A retry re-queues every failed, upstream_failed, and cancelled task — plus everything downstream of them — and reactivates the run. Tasks that already succeeded keep their results and are not re-run. The re-queued tasks execute against the current DAG definition, so the fix-then-retry loop is exactly: edit the YAML, save, retry. If the run is still active, or nothing in it failed, the API answers with a conflict instead of retrying.
You can also target a single task; its downstream tasks are cleared with it:
Check it:
The run flips back to running, transform and load execute again, and the STATE column lands on success. In the console, the same run's history now shows the fresh attempts.
Tip
Manual retry is for after-the-fact recovery. For failures you can
anticipate — flaky networks, busy databases — give tasks automatic
retries and retry_delay instead, covered in
Retries, timeouts & pools.
Override a state by hand: cronova mark¶
Sometimes re-running is wrong — you already fixed the data by hand, or a task is stuck and you want the pipeline to move on. cronova mark is the operator override:
cronova mark <run_id> <state> # run: success | failed
cronova mark <run_id> <task_id> <state> # task: success | failed | skipped
Say transform failed but you ran the transformation manually. Mark it done and let the run continue:
Marking a task success or skipped releases the downstream tasks that were upstream_failed because of it — the scheduler picks them up on the next tick and the run resumes from where it was blocked. Marking works on an active run too: a still-running task's process is killed first, then the state you chose wins.
One subtlety: the default all_success rule treats a skipped upstream as blocking. If a task should tolerate skipped upstreams, give it trigger_rule: none_failed — "no upstream failed; success or skipped is fine".
Run-level mark corrects a finished run's recorded outcome — for example, declaring a run success after you've dealt with its failure out-of-band:
Check it — cronova runs daily_etl -n 1 reflects the override immediately, and every trigger, cancel, retry, and mark is recorded in the server's operations audit log, so overrides are never invisible.
What you learned¶
depsdraws the graph edges; every DAG file is cycle-checked on load, and a task fires when its upstreams satisfy itstrigger_rule(default:all_success).- A failure blocks only its descendants — they end
upstream_failed— while parallel branches finish; any failed or blocked task makes the runfailed. - Six trigger rules cover the rest:
all_donefor cleanup,one_failedfor in-run failure handling,none_failedto tolerate skipped upstreams, plusone_successandall_failed. cronova retry <run_id> [task_id]re-queues the failed parts of a finished run against the current DAG definition;cronova mark <run_id> [task_id] <state>is the manual override that can unblock or correct a run.
Next: parameterize your commands with the run's logical date and friends in Template variables.